ローカル環境で日本語を学習させたLLM Elyzaの実行を試した

# はじめに

この記事ではローカル環境で日本語を学習させたLLM Elyzaの実行を試します。

[ollamaを使ってローカルでLLMを動かしてみた - Qiita](https://qiita.com/u1and0/items/8c448a7e87182b34ab94#dockerを再起動)

の続きです。

![Peek 2024-04-08 05-20.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/eb13f21e-1862-657e-1bad-e2a7bb2c1066.gif)

## 参考

[Ollama で Elyza-7B を試す｜npaka](https://note.com/npaka/n/ndadbae6c6be5)

## 必要となる技術

- Docker
- ollama
- Deno/TypeScript (optional)

## 環境

- OS: Ubuntu22.04 on Docker Archlinux
- GPU: NVIDIA Corporation GP107 [GeForce GTX 1050 Ti]
- CPU: Intel(R) Core(TM) i5-8400 CPU @ 2.80GHz
- ﾒﾓﾘ: 32GB

# 手順

## ggufファイルのダウンロード

こちらではggufファイルへ変換されたものが用意されています。

[mmnga/ELYZA-japanese-Llama-2-7b-instruct-gguf at main](https://huggingface.co/mmnga/ELYZA-japanese-Llama-2-7b-instruct-gguf/tree/main)

```shell-session
$ curl -LO https://huggingface.co/mmnga/ELYZA-japanese-Llama-2-13b-fast-instruct-gguf/resolve/main/ELYZA-japanese-Llama-2-13b-fast-instruct-q4_K_M.gguf
```

ELYZA-japanese-Llama-2-13b-fast-instruct-q4_K_Mを選択しました。以下でモデル名に続く用語の意味を解説します。


### 13b

7b, 13b, 70bがパラメータ数で、数字が大きくなるほど回答が賢い代わりに、返答速度が遅く、ファイルが重くなります。

必要なメモリの容量も大きくなります。

> Note: You should have at least 8 GB of RAM available to run the 7B models, 16 GB to run the 13B models, and 32 GB to run the 33B models.[^1]
> 

[^1]: [GitHub - ollama/ollama: Get up and running with Llama 2, Mistral, Gemma, and other large language models.](https://github.com/ollama/ollama/tree/main#model-library)

### fast-instruct[^2][^3]

> 今回ベースとした
> 

> [meta-llama/Llama-2-7b-chat-hf](https://huggingface.co/meta-llama/Llama-2-7b-chat-hf)
> 

> には32,000トークンが含まれますが、それらの語彙は日本語に最適化されていないため、日本語を取り扱うためのコストは英語に比べて高くなっております。
> 

> 具体的には「こんにちは」は
> 

> こ/ん/に/ち/は
> 

> 、「東京都」は
> 

> 東/京/都
> 

> のように1文字あたり1トークンに対応していたり、「肉」のような元のLlama 2事前学習時に低頻度であったと思しき漢字はバイト単位で3トークンで表現されていたり、といった形です。 (いっぽう「Hello」や「World」といった英単語はそれぞれ1トークンで表現されています)
> 

[^2]:[https://zenn.dev/elyza/articles/2fd451c944649d#日本語語彙の追加-(elyza-japanese-llama-2-7b-fast、ないし...-fast-instructのみ該当)](https://zenn.dev/elyza/articles/2fd451c944649d#%E6%97%A5%E6%9C%AC%E8%AA%9E%E8%AA%9E%E5%BD%99%E3%81%AE%E8%BF%BD%E5%8A%A0-(elyza-japanese-llama-2-7b-fast%E3%80%81%E3%81%AA%E3%81%84%E3%81%97...-fast-instruct%E3%81%AE%E3%81%BF%E8%A9%B2%E5%BD%93))

[^3]: [ELYZAが公開した日本語LLM「ELYZA-japanese-Llama-2-7b」についての解説 : (1) 事前学習編](https://zenn.dev/elyza/articles/2fd451c944649d#日本語語彙の追加-(elyza-japanese-llama-2-7b-fast、ないし...-fast-instructのみ該当))

### q4_K_M

> Q2~Q8、KとかSとかMなどの記号が書かれたモデルたちがあると思います。
> 

> 基本的にQ2~Q8は量子化するビット数で、数字が小さいほどモデルと精度が低下します。
機械学習のモデルが32bit~16bitのfloat型で学習されていて、そのモデルを更にbit数を減らしたものがQ2~Q8です。当たり前ですが、bit数が少ないので軽量になって、高速で計算できるようになります。
> 

> ネット上のいくつかの記事を見たところ、Q4でも許容できるという話があったので今回はQ4の量子化モデルを選びます。[^4]
> 

> また、KとかSとかMですが、量子化の方式を表しています。
K、K_S、K_M、K_Lの順でモデルサイズと精度が反比例する量子化方式です。また、無印のKがつかない量子化モデルもありますが(Q4_0.ggufなど)、これはレガシーな量子化方式らしく、非推奨らしいです。
> 

> これらを踏まえて、今回はQ4_K_Mを選んでダウンロードすることにしました。[^4]


[^4]: [CPUのみのローカルLLMサーバー構築(llama.cpp)](https://zenn.dev/emoclew/articles/9dfa7fd948a18b)


## Modelfileを取得

```docker
$ docker exec -it elyza ollama show llama2:7b-chat --modelfile > Modelfile

```

llama2のモデルファイルをollama showサブコマンドで取得します。

## Modelfileを修正

```docker
# Modelfile generated by "ollama show"
# To build a new Modelfile based on this one, replace the FROM line with:
# FROM llama2:7b-chat

FROM /root/.ollama/models/manifests/registry.ollama.ai/library/elyza/ELYZA-japanese-Llama-2-13b-fast-instruct-q4_K_M.gguf
TEMPLATE """
 
{{ .System }}


{{ .Prompt }} 

"""
PARAMETER stop "
"
PARAMETER stop "
"
PARAMETER stop "
"
PARAMETER stop "
"

```

Modelfileの内容を変更しました。

FROMのパスをggufファイルがある場所にするだけです。

dockerで動かすので、volumeのアタッチ先のパス`/root/.ollama/…`を指定します。

## モデルの作成

```docker
$ docker exec -it elyza ollama create elyza:13b-fast-instruct -f /root/.ollama/models/elyza/Modelfile
transferring model data
creating model layer
creating template layer
creating parameters layer
creating config layer
using already created layer sha256:3acf4cb2c839b0b6eb3f040f1eb87cfa21c057eb406f608d3daa31cade5aba21
writing layer sha256:1507233b03785311a99a1beaf27f72d5a1c15abb3d2deba635defb2c68059262
using already created layer sha256:fa304d6750612c207b8705aca35391761f29492534e90b30575e4980d6ca82f6
writing layer sha256:524b6d9a976e89de0576000773ced0774485e7748903fb3a1a50c0a5e47d5c1d
writing manifest
success

```

elyza:13b-fast-instructモデルとしてcreateする。

14分もかかった！

パスはこうなった

[ 18G]  .
├── [ 11G]  blobs
│   ├── [  62]  sha256-1507233b03785311a99a1beaf27f72d5a1c15abb3d2deba635defb2c68059262
│   ├── [  59]  sha256-2e0493f67d0c8c9c68a8aeacdf6a38a2151cb3c4c1d42accf296e19810527988
│   ├── [7.4G]  sha256-3acf4cb2c839b0b6eb3f040f1eb87cfa21c057eb406f608d3daa31cade5aba21
│   ├── [ 557]  sha256-42ba7f8a01ddb4fa59908edd37d981d3baa8d8efea0e222b027f29f7bcae21f9
│   ├── [ 412]  sha256-524b6d9a976e89de0576000773ced0774485e7748903fb3a1a50c0a5e47d5c1d
│   ├── [4.7K]  sha256-7c23fb36d80141c4ab8cdbb61ee4790102ebd2bf7aeff414453177d4f2110e5d
│   ├── [3.6G]  sha256-8934d96d3f08982e95922b2b7a2c626a1fe873d7c3b06e8e56d7bc0a1fef9246
│   ├── [6.9K]  sha256-8c17c2ebb0ea011be9981cc3922db8ca8fa61e828c5d3f44cb6ae342bf80460b
│   └── [  91]  sha256-fa304d6750612c207b8705aca35391761f29492534e90b30575e4980d6ca82f6
├── [ 598]  elyza
│   └── [ 446]  Modelfile
└── [7.4G]  manifests
└── [7.4G]  [registry.ollama.ai](http://registry.ollama.ai/)
└── [7.4G]  library
├── [7.4G]  elyza
│   ├── [ 708]  13b-fast-instruct
│   ├── [7.4G]  ELYZA-japanese-Llama-2-13b-fast-instruct-q4_K_M.gguf
│   └── [ 90K]  README.html
└── [1.1K]  llama2
└── [1003]  7b-chat

18G used in 7 directories, 14 files

[]カッコ内はファイルサイズです。

/blobsのなかにイメージが作られるようです。

/elyzaディレクトリに修正したModelfileを作成しました。

## モデルの実行

```docker
$ docker run -t --gpus=all -v `pwd`:/root/.ollama -p 11434:11434 --name elyza ollama/ollama
$ docker exec -it elyza ollama run elyza:13b-fast-instruct
```

![Peek 2024-04-08 05-20.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/eb13f21e-1862-657e-1bad-e2a7bb2c1066.gif)


### Denoで実行

ollamaを立ち上げているURL/IPアドレスに適宜変えて、`Deno.stdout.writeSync`を使うと、非同期に改行せずに結果を表示することができます。

サンプルとして以下を参考にしました。

[langchainjs/examples/src/models/llm/ollama.ts at d6e25af137873493d30bdf5732d46b842e421ffa · langchain-ai/langchainjs](https://github.com/langchain-ai/langchainjs/blob/d6e25af137873493d30bdf5732d46b842e421ffa/examples/src/models/llm/ollama.ts)

```tsx
import { Ollama } from "npm:@langchain/community/llms/ollama";

const ollama = new Ollama({
  baseUrl: "http://localhost:11434",
  model: "elyza:13b-fast-instruct",
});
const stream = await ollama.stream("お名前はなんですか？");
// const chunks = [];
for await (const chunk of stream) {
  // chunks.push(chunk);
  Deno.stdout.writeSync(new TextEncoder().encode(chunk));
}
// console.log(chunks.join(""));
```

ollama.tsとして保存して、`deno run -A ollama.ts` で実行します。最小限の許可はnetとenvだけallowすればいいんだろうか？

![Peek 2024-04-08 05-37.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/e019c7eb-13a9-e51e-034c-f78222eff895.gif)


# まとめ

- gguf形式のモデルファイルをダウンロードできる場所を探すのに手間取りました。
- モデルのダウンロードに8GBの容量と20分くらいの時間がかかります。
- OpenAI APIやAnthropic APIに使い慣れていると苛つくレベルで遅いですが、個人の家庭用PCで安いGPUでも実行できていることがすごい！
- 実行はできるけど実用レベルの速度は得られず、今はまだAPIを使って月数ドルのお金を払ったほうが良さそうかな、と思いました。