DeepSeek R1 Dynamic 1.58-bit 量子化されたモデルをWindows 11(WSL)で動かしてみる

今回は、DeepSeek を 1.58bit 量子化したモデルを、Windows 11で動かしたときのメモです。

# はじめに
今回、最新の llama.cpp と DeepSeek-R1-GGUF モデルを使用して、LLM の動作確認を行いましたのでメモを書き残しておきます。
チャット形式の対話プロンプトとして <｜User｜> と <｜Assistant｜> のトークンを利用しています。

# 使用した Windows 11 の環境
CPU: Intel(R) Core(TM) i9-10850K CPU @ 3.60GHz
メモリ: 64.0 GB
GPU 0: NVIDIA GeForce RTX 3090
HDD: 5.5 TB
WSL: Ubuntu 24.04.1 LTS

いつもは、Docker 環境を使用していますが、今回はリソースの関係で WSL直接実行しています。

Ubuntu 24.04.1のインストール

https://apps.microsoft.com/detail/9NZ3KLHXDJP5?hl=neutral&gl=JP&ocid=pdpshare

WSLのインストール

https://learn.microsoft.com/ja-jp/windows/wsl/install#install-wsl-command

WSLのCUDAインストール

https://docs.nvidia.com/cuda/wsl-user-guide/index.html#cuda-support-for-wsl-2

WLS の Ubuntu に CUDA をインストール
```bash
sudo apt-key del 7fa2af80
wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
sudo apt-get -y install cuda-toolkit-12-8
```

WSL の Ubuntu に cuDNN をインストール
```bash
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
sudo apt-get -y install cudnn
```

# Ubuntu で llama.cpp のビルド
まずは、スタートメニューから、Ubuntu 24.04 LTS を起動した後に、llama.cpp を GitHub から clone し、以下の手順でビルドを行います。

```bash
# パッケージの更新と必要なパッケージのインストール
sudo apt-get update
sudo apt-get install build-essential cmake curl libcurl4-openssl-dev -y

# 実行ファイルのパスとライブラリのパスを設定
export PATH=/usr/local/cuda-12.8/bin${PATH:+:${PATH}}
export LD_LIBRARY_PATH=/usr/local/cuda-12.8/lib ${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

# リポジトリのクローン
git clone https://github.com/ggerganov/llama.cpp

# ビルド用ディレクトリを指定して cmake を実行
cmake llama.cpp -B llama.cpp/build -DBUILD_SHARED_LIBS=OFF -DGGML_CUDA=ON -DLLAMA_CURL=ON

# ビルドの実行（Release モード、クリーンビルド、複数ターゲットを指定）
cmake --build llama.cpp/build --config Release -j --clean-first --target llama-quantize llama-cli llama-gguf-split

# ビルドした実行ファイルを llama.cpp ディレクトリにコピー
cp llama.cpp/build/bin/llama-* llama.cpp
```

# Python のインストール
以下のサイトに従ってPythonをインストール

https://docs.anaconda.com/miniconda/install/

```bash
# インストーラーをダウンロード
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
# インストール実行
bash ~/Miniconda3-latest-Linux-x86_64.sh
# 環境を適用する
source ~/.bashrc
```

# Ubuntu で DeepSeek-R1-GGUF モデルのダウンロード
次に、Hugging Face Hub を利用して DeepSeek-R1-GGUF モデルをダウンロードします。
以下のように Pyhon をインストール後、Python パッケージをインストールして、Python スクリプトを実行してください。

必要なパッケージのインストール
```bash
# パッケージインストール
pip install huggingface_hub hf_transfer
```

```python:download.py
import os
# ダウンロードを高速化するための環境変数設定（任意）
os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "1"

from huggingface_hub import snapshot_download

snapshot_download(
  repo_id = "unsloth/DeepSeek-R1-GGUF",
  local_dir = "DeepSeek-R1-GGUF",
  allow_patterns = ["*UD-IQ1_S*"],  # 1.58bit 用の UD-IQ1_S を選択
)
```

DeepSeek-R1-UD-IQ1_Sのモデルをダウンロード
```bash
python download.py
```

# 実行
動作確認のため、以下のコマンドを実行してみました。
この例では、キャッシュタイプに q4_0 を指定し、指定のスレッド数、温度 (--temp)、コンテキストサイズ (--ctx-size)、シード値 (--seed)、RTX 3090 24GB のメモリを使うために GPU に複数レイヤーをオフロード（-n-gpu-layers 7）して処理しています。
また、対話プロンプトには <｜User｜> と <｜Assistant｜> のトークンを用いて、ユーザーからの指示とアシスタントの応答が交互に生成される形式にしています。

```
./llama.cpp/llama-cli \
  --model DeepSeek-R1-GGUF/DeepSeek-R1-UD-IQ1_S/DeepSeek-R1-UD-IQ1_S-00001-of-00003.gguf \
  --cache-type-k q4_0 \
  --threads 12 -no-cnv --prio 2 \
  --n-gpu-layers 7 \
  --temp 0.6 \
  --ctx-size 8192 \
  --seed 3407 \
  --prompt "<｜User｜>Create a Flappy Bird game in Python.<｜Assistant｜>"
```

# 結果

凄く時間かけて出力された結果は以下の通り。

---
Create a Flappy Bird game in Python.<think>
Okay, the user wants me to create a Flappy Bird game in Python. Hmm, I remember that Flappy Bird is a simple game where a bird flaps between obstacles. So, I need to think about how to approach this.

First, I should consider which Python libraries to use. Pygame is a popular library for making games. I'll use that. So, the first step is to set up the Pygame environment. I'll need to initialize Pygame and create a window with a specific size. Then, set a caption for the window.

Next, the game elements: the bird and the obstacles. The bird needs to have gravity, so when the user presses a key, it flies up. The obstacles are pipes that move from the right side of the screen. The bird must avoid colliding with them.

For the bird, I'll need a sprite. Maybe use images, but maybe start with simple shapes. Wait, the user might want a basic version. So perhaps use a circle for the bird and rectangles for the pipes. But Pygame can handle images. Wait, maybe load images for the bird and pipes. The code should load images, set their positions, and handle their movement.

Handling user input is crucial. The bird should jump when the user presses a key, like the spacebar or up arrow. Each jump should apply a negative velocity to make the bird move up. Also, gravity should constantly pull the bird down by increasing the velocity each frame.

For the pipes, I'll need to generate them at certain intervals. Maybe create a pair of pipes (top and bottom) with a gap. The pipes move from right to left. When they reach the left edge, they're removed. The user scores when passing a pipe.

Collision detection is important. The bird must not touch any pipes. Check for collisions between the bird's rectangle and each pipe's rectangle. If collision occurs, end the game.

Also, the game loop needs to handle events, update positions, and redraw the screen each frame. FPS control is necessary to keep the game running smoothly.

Scoring: every time the bird passes a pair of pipes, increment the score. Display the score on the screen using Pygame's font rendering.

Putting this all together, the code structure would be:

1. Initialize Pygame, set up the window.
2. Load images for bird, pipes, background.
3. Define classes for Bird and Pipe.
4. Set up game loop with clock for FPS.
5. In the loop, handle events (key presses, quit events).
6. Update bird position based on velocity and gravity.
7. Generate new pipes at intervals, move existing ones.
8. Check for collisions, if so, end game.
9. Draw everything on the screen, including score.
10. Manage game states (start, running, game over).

Wait, the user might need a complete code example. So let me draft the code step by step.

First, import pygame and necessary modules. Then define the Bird class with movement logic. The Pipe class to manage the obstacles. Then, the main game loop where everything is managed. Also, handle the score display.

Wait, in the code, the Bird's jump is triggered by space key. The pipes are generated with random heights. The collision is checked using pygame's Rect collisions. The game loop runs until the player quits or collides.

Possible issues: making sure the pipes are generated correctly, managing their movement, handling the bird's physics. Also, the score updates correctly when the bird passes a pipe.

Testing the code would help. Maybe start by writing the code step by step, ensuring each part works. But since it's a code example, I'll outline the code with comments explaining each part.

So the final code should have:

0. Import pygame and sys, initialize.
1. Define constants like screen size, gravity, bird properties.
2. Bird class with update and draw methods.
3. Pipe class with movement and off-screen check.
4. Main game loop with event handling, bird updates, pipe generation.
5. Collision checks, score tracking.
6. Displaying score using render functions.

Yes, that's the structure. Now, writing the code accordingly, making sure to include all necessary parts. Make sure to load images or use shapes if images are not available. But in the example, using images would be better, but for simplicity, maybe use rects. Wait, the code in the answer uses images, so I need to have the paths for bird and pipe images. Alternatively, maybe generate pipes as rectangles.

But given that the user wants a working example, perhaps using images. The code would need to load those images. So the code should have paths to the images. However, for the user to run it, they need to have those images. Alternatively, maybe the code uses pygame's drawing functions to create the bird and pipes as rectangles. That might be simpler for a self-contained example.

Wait, in the answer code provided earlier, the bird and pipes are represented as images. But if someone runs the code without the images, it would crash. So maybe in the code, use pygame's Surface and Rect to create the bird and pipes as rectangles. So adjust the code to use rects instead of images.

Alternatively, use the images but note that the user needs to have them. Hmm. The original answer uses images, but perhaps in the code, generate the bird and pipes as rectangles. Let me think.

In the code example I'll write, perhaps the bird is a yellow circle (using pygame.draw.circle) and the pipes are green rectangles. That way, the code is self-contained and doesn't require external images. So adjust the code to use shapes instead of images.

Alternatively, use the pygame.Surface to create a simple bird and pipe sprites. For example:

bird = pygame.Surface((30, 30))
bird.fill((255, 255, 0))  # Yellow color
pipe = pygame.Surface((50, height))
pipe.fill((0, 255, 0))  # Green

But using such surfaces would require creating them each time. Alternatively, use rects and draw them.

But handling collision with rects is easier. So in the code, represent the bird as a rect, and pipes as rects. Move them accordingly.

So, revising the code to use rects instead of images. That way, no external files are needed. The code can be run as-is.

So, putting all that together, here's the code.
</think>

To create a Flappy Bird game in Python, we'll use the Pygame library. Here's a step-by-step explanation and code:

1. **Install Pygame**: First, install Pygame using `pip install pygame`.

2. **Game Structure**:
   - The bird is represented by a rectangle.
   - Pipes are vertical rectangles moving leftwards.
   - The bird can jump using the spacebar.
   - Score increases when passing pipes.

3. **Code**:

```python
import pygame
import sys
import random

# Initialize Pygame
pygame.init()

# Screen dimensions
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 600

# Set up the game window
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Flappy Bird")

# Colors
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
YELLOW = (255, 255, 0)

# Bird properties
BIRD_WIDTH = 30
BIRD_HEIGHT = 30
GRAVITY = 1
JUMP_VELOCITY = -20

# Pipe properties
PIPE_WIDTH = 50
PIPE_GAP = 150
PIPE_SPEED = 3
PIPE_FREQUENCY = 1500  # Milliseconds between pipe spawns

# Scoring
score = 0
font = pygame.font.Font(None, 36)

# Clock for FPS control
clock = pygame.time.Clock()

class Bird:
    def __init__(self):
        self.x = SCREEN_WIDTH // 4
        self.y = SCREEN_HEIGHT // 2
        self.velocity = 0
        self.rect = pygame.Rect(self.x, self.y, BIRD_WIDTH, BIRD_HEIGHT)
    
    def update(self):
        self.velocity += GRAVITY
        self.y += self.velocity
        self.rect.y = self.y
    
    def jump(self):
        self.velocity = JUMP_VELOCITY

class Pipe:
    def __init__(self):
        self.x = SCREEN_WIDTH
        self.height = random.randint(100, SCREEN_HEIGHT - PIPE_GAP - 100)
        self.top = pygame.Rect(self.x, 0, PIPE_WIDTH, self.height)
        self.bottom = pygame.Rect(self.x, self.height + PIPE_GAP, PIPE_WIDTH, SCREEN_HEIGHT - (self.height + PIPE_GAP))
        self.passed = False
    
    def update(self):
        self.x -= PIPE_SPEED
        self.top.x = self.x
        self.bottom.x = self.x
    
    def offscreen(self):
        return self.x + PIPE_WIDTH < 0

# Game variables
bird = Bird()
pipes = []
last_pipe = pygame.time.get_ticks()

running = True
game_over = False

# Main game loop
while running:
    clock.tick(60)
    
    # Handle events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE and not game_over:
                bird.jump()
    
    if not game_over:
        # Spawn new pipes
        current_time = pygame.time.get_ticks()
        if current_time - last_pipe > PIPE_FREQUENCY:
            pipes.append(Pipe())
            last_pipe = current_time
        
        # Update bird and pipes
        bird.update()
        for pipe in pipes:
            pipe.update()
            if pipe.top.x + PIPE_WIDTH < bird.x and not pipe.passed:
                score += 1
                pipe.passed = True
        
        # Remove off-screen pipes
        pipes = [pipe for pipe in pipes if not pipe.offscreen()]
        
        # Check collisions
        for pipe in pipes:
            if bird.rect.colliderect(pipe.top) or bird.rect.colliderect(pipe.bottom):
                game_over = True
        
        if bird.y < 0 or bird.y + BIRD_HEIGHT > SCREEN_HEIGHT:
            game_over = True
    
    # Draw everything
    screen.fill(WHITE)
    pygame.draw.rect(screen, YELLOW, bird.rect)
    for pipe in pipes:
        pygame.draw.rect(screen, GREEN, pipe.top)
        pygame.draw.rect(screen, GREEN, pipe.bottom)
    
    # Display score
    text = font.render(f"Score: {score}", True, (0, 0, 0))
    screen.blit(text, (10, 10))
    
    pygame.display.update()

pygame.quit()
sys.exit()
```

**How to Play**:
; - Press SPACE to make the bird jump.
- Avoid the green pipes.
; - The score increases when you pass a pipe.

**Mechanics**:
- The bird falls due to gravity and jumps when you press the spacebar.
 Pipes spawn at intervals and move left.
- Colliding with a pipe or the screen boundaries ends the game. [end of text]

---

# 遊んでみる

せっかくなので、Pythonコード部分をファイル名（FlappyBird.py）に保存してプログラムを実行します。

こちらはWSLではなく、WindowsのPythonを使用しました。
```
pip install pygame
python FlappyBird.py
```

遊べるが、難しい。。。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32580/57241ae6-931b-ae54-e2c5-310449697806.png)

# 生成速度

モデルをUSB HDDに入れていたことが影響しているのかもしれませんが、82時間かかりました。。。メモリは使いますが、CPU や GPU の使用率は気にならないくらい使われていない。

```
llama_perf_sampler_print:    sampling time =     281.92 ms /  2458 runs   (    0.11 ms per token,  8718.85 tokens per second)
llama_perf_context_print:        load time = 7366434.07 ms
llama_perf_context_print: prompt eval time = 1702551.09 ms /    12 tokens (141879.26 ms per token,     0.01 tokens per second)
llama_perf_context_print:        eval time = 294244354.64 ms /  2445 runs   (120345.34 ms per token,     0.01 tokens per second)
llama_perf_context_print:       total time = 295948235.85 ms /  2457 tokens
```

# 他のビットオプションの比較
DeepSeek-R1 には複数の MoE (Mixture of Experts) ビットオプションが用意されています。使用目的に合わせて精度やモデルサイズのトレードオフを選択する際の参考にしてください。今回は、一番精度の低い「UD-IQ1_S」を使用しました。

|MoE Bits|タイプ|サイズ|精度|
|---|---|---|---|
|1.58bit|UD-IQ1_S|131GB|普通|
|1.73bit|UD-IQ1_M|158GB|良好|
|2.22bit|UD-IQ2_XXS|183GB|より良い|
|2.51bit|UD-Q2_K_XL|212GB|最高|

# さいごに
凄く時間がかかったけど、自分の環境で高精度なLLMが動くのは凄いですね。

# 参考記事

https://unsloth.ai/blog/deepseekr1-dynamic

https://huggingface.co/unsloth/DeepSeek-R1-GGUF/blob/main/README.md