mzwing

mzwing

Every moment we spent together is well worth recalling.
github
tg_channel
x
email
pixiv
bilibili
gitlab
zhihu
facebook
instagram

碎記·RWKVの邪典量化(llama.cppのみ)

環境準備ニャ#

私の水、硬水だ(x

詳しくは言いたくないので、直接コードを示した方が効果的だ(

#!/usr/bin/sh

llama_cpp_version="b4519"

user="mzwing"

# 必要なフォルダを作成
mkdir -p /home/$user/AI/repo/
mkdir -p /home/$user/AI/runner/
mkdir -p /home/$user/AI/model/

# llama.cpp リポジトリをインストール
cd /home/$user/AI/repo/
git clone https://github.com/ggerganov/llama.cpp.git --depth 1
rye init llama_cpp
cd ./llama_cpp/
rye add numpy sentencepiece transformers gguf protobuf torch

# llama.cpp バイナリをインストール
cd /home/$user/AI/runner/
mkdir -p ./llama.cpp/
cd ./llama.cpp/
aria2c -c -x16 "https://github.com/MZWNET/actions/releases/download/llama_cpp-$llama_cpp_version/llama-$llama_cpp_version-bin-linux-avx2-intel-mkl-x64.zip"
unzip "llama-$llama_cpp_version-bin-linux-avx2-intel-mkl-x64.zip"
rm -rf "llama-$llama_cpp_version-bin-linux-avx2-intel-mkl-x64.zip"
aria2c -c -x16 https://gist.github.com/bartowski1182/eb213dccb3571f863da82e99418f81e8/raw/b2869d80f5c16fd7082594248e80144677736635/calibration_datav3.txt

# Huggingface CLI をインストール
cd /home/$user/AI/repo/
rye init huggingface_cli
cd ./huggingface_cli/
rye add huggingface_hub[hf_transfer]

# RWKV 関連の環境をインストール
cd /home/$user/AI/repo/
rye init rwkv
cd ./rwkv/
rye add torch numpy

# ホームに戻る
cd /home/$user/

何?なぜ$userを使うのか?次の文で明らかになる(((

ここまでで、皆さんは私の石山コードについての心理的期待を持っているはずだ、焦らないで、次はもっと石(x

簡単に言うと、なぜllama.cpp内で直接rye initを実行しないかというと、llama.cppの公式はpoetryを使用しているが、創造的な mzw が公式の提案に従うわけがない(x)、だから新しいディレクトリを作成し、私のryeを使い続けることにした!(x

venvで HF CLI をインストールするのは、rye install huggingface_hub[hf_transfer]ではhuggingface-cliというコマンドが出ないからだ(イライラ、クソrye

そして…… ほぼ何もなかった#

次に直面した問題は重いもので…… 長い間探しても RWKV を Huggingface フォーマットに変換する方法や、直接 pth から gguf に変換する方法が見つからなかった。明らかに RWKV の公式は Transformers と一緒に変換スクリプトを発表していたが、そのスクリプトは動かなかった(本当に驚いた、何の草台班子だ)。これで困難に陥り、2、3 日探し回り、ほぼ諦めかけていた。

一度 Deepseek に書いてもらおうかとも思ったが、明らかに Deepseek は RWKV6 の構造を理解しておらず、適当に書かれてしまったので、断念した。

最後には我慢できず!btaskel 大佬のところに行ってDiscussionを発表した(工事現場の英語で交流(x))、自分でも他の解決策を試み、最終的に最適解を見つけた……Thanks btakel 大佬 very much!

使用したスクリプトは大体こんな感じ:

# convert_rwkv6_to_hf.py
# Original code from <https://rwkv.cn/llamacpp#appendix-code>
# Edited by mzwing<mzwing@mzwing.eu.org>
# Convert the model for the pytoch_model.bin
import sys
import torch

if len(sys.argv) != 3:
    print(f"RWKV6.0 pth (non-huggingface) チェックポイントをHuggingfaceフォーマットに変換します")
    print("Usage: python convert_rwkv6_to_hf.py SOURCE_MODEL TARGET_MODEL")
    exit()

SOURCE_MODEL = sys.argv[1]
TARGET_MODEL = sys.argv[2]

# ターゲットモデルを削除
import os

if os.path.exists(TARGET_MODEL):
    os.remove(TARGET_MODEL)

model = torch.load(SOURCE_MODEL, mmap=True, map_location="cpu")

# すべてのキーの名前を変更し、"rwkv."を含める
new_model = {}
for key in model.keys():

    # キーが"blocks"で始まる場合
    if key.startswith("blocks."):
        new_key = "rwkv." + key
        # .att.を.attention.に置き換える
        new_key = new_key.replace(".att.", ".attention.")
        # .ffn.を.feed_forward.に置き換える
        new_key = new_key.replace(".ffn.", ".feed_forward.")
        # `0.ln0.`を`0.pre_ln.`に置き換える
        new_key = new_key.replace("0.ln0.", "0.pre_ln.")
    else:
        # 名前変更は不要
        new_key = key

        # `emb.weight`を`rwkv.embeddings.weight`に名前変更
        if key == "emb.weight":
            new_key = "rwkv.embeddings.weight"

        # `ln_out.x`を`rwkv.ln_out.x`に名前変更
        if key.startswith("ln_out."):
            new_key = "rwkv." + key

    print("Renaming key:", key, "--to-->", new_key)
    new_model[new_key] = model[key]

# 新しいモデルを保存
print("Saving the new model to:", TARGET_MODEL)
torch.save(new_model, TARGET_MODEL)

#!/usr/bin/sh

author="Seikaijyu"
model="RWKV6-7B-v3-porn-chat"
suffix=""
size="7B"

user="mzwing"

# 必要なフォルダを作成
mkdir -p /home/$user/AI/model/$model$suffix-original/
mkdir -p /home/$user/AI/model/$model$suffix/

# 元のモデルをダウンロード
aria2c -c -x16 "https://huggingface.co/$author/$model/resolve/main/$model$suffix.pth?download=true" -d /home/$user/AI/model/$model$suffix-original/ -o $model$suffix.pth

# RWKV6 設定ファイルをダウンロード
GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/RWKV/v6-Finch-$size-HF /home/$user/AI/model/$model$suffix/
rm -rf /home/$user/AI/model/$model$suffix/*.bin
rm -rf /home/$user/AI/model/$model$suffix/*.safetensors

# 元のモデルをHFフォーマットに変換
source /home/$user/AI/repo/rwkv/.venv/bin/activate
python /home/$user/convert_rwkv6_to_hf.py /home/$user/AI/model/$model$suffix-original/$model$suffix.pth /home/$user/AI/model/$model$suffix/pytorch_model.bin

# クリーンアップ
rm -rf /home/$user/AI/model/$model$suffix-original/

authormodelは量子化するモデルを制御するために使用され、suffixSeikaijyu/RWKV6-7B-v3-porn-chatのように、1 つのリポジトリの下にいくつかのバリエーションを配置する場合に使用される。

sizeは変換するモデルのパラメータ量に対応し、例えば1B6のようなもの(正確には基底モデルのもので、一般的には偏差はあまり大きくないが(x

リンクの部分は自分で変更してくれ、私は石山に触れたくない(x

ここでは邪典が満載と言える…… 私は本当に考えもしなかった、元のモデルの設定をクローンしてpytorch_model.binを置き換えるだけで HF フォーマット化が完了するとは……

また、RWKV v6 world シリーズモデルであっても、RWKV 普通モデルの HF 設定を使用することを忘れないでください。そうしないと、下のllama.cpp変換が失敗します!vocabが不足していると教えてくれます。(RWKV v6 llama.cpp PR の実装時に world モデルが考慮されていなかった疑いがあり、結果的に world も使用できることがわかりました、まあそれならそれでいいでしょう、というわけでこんな奇妙な状況が生まれました……)

ついにconvert_hf_to_gguf.pyが起動できる……#

# モデルをgguf F16フォーマットに変換
mkdir -p /home/$user/AI/model/$model$suffix-GGUF/
source /home/$user/AI/repo/llama_cpp/.venv/bin/activate
cd /home/$user/AI/repo/llama.cpp/
python ./convert_hf_to_gguf.py --outtype f16 --outfile /home/$user/AI/model/$model$suffix-GGUF/$model$suffix.F16.gguf /home/$user/AI/model/$model$suffix/

# クリーンアップ
rm -rf /home/$user/AI/model/$model$suffix/

# ホームに戻る
cd /home/$user/

ここは私が多くを語る必要はないだろう(x)標準的なllama.cpp変換プロセスだ(((

量子化、起動#

自動量子化のための小さなスクリプトを作成した(怠け者だと思われる)

#!/usr/bin/sh

model="RWKV6-7B-v3-porn-chat"
suffix=""
HF_TOKEN="xxx"

user="mzwing"

cd /home/$user/AI/runner/llama.cpp/

# ログイン
source /home/$user/AI/repo/huggingface_cli/.venv/bin/activate
huggingface-cli login --token $HF_TOKEN

# F16モデルをアップロード
HF_HUB_ENABLE_HF_TRANSFER=1 huggingface-cli upload --repo-type model --commit-message "GGUF model commit (made with llama.cpp $llama_cpp_version)" "$model$suffix-GGUF" "/home/$user/AI/model/$model$suffix-GGUF/$model$suffix.F16.gguf"

# imatrixを生成
echo -e "imatrixを生成中...\n"
./llama-imatrix -m "/home/$user/AI/model/$model$suffix-GGUF/$model$suffix.F16.gguf" -f ./calibration_datav3.txt -o "/home/$user/AI/model/$model$suffix-GGUF/$model$suffix.imatrix"
HF_HUB_ENABLE_HF_TRANSFER=1 huggingface-cli upload --repo-type model --commit-message "GGUF model commit (made with llama.cpp $llama_cpp_version)" "$model$suffix-GGUF" "/home/$user/AI/model/$model$suffix-GGUF/$model$suffix.imatrix"

# 量子化
params=( "Q8_0" "Q6_K" "Q5_K_M" "Q5_K_S" "Q5_1" "Q5_0" "Q4_K_M" "Q4_K_S" "Q4_1" "Q4_0" "Q3_K_L" "Q3_K_M" "Q3_K_S" "Q2_K_S" "Q2_K" "IQ4_XS" "IQ4_NL" "IQ3_XS" "IQ3_M" "IQ3_S" "IQ3_XXS" "IQ2_M" "IQ2_S" "IQ2_XS" "IQ2_XXS" "IQ1_M" "IQ1_S" "TQ2_0" "TQ1_0" )
for param in "${params[@]}"; do
    echo -e "$paramに変換中...\n"
    ./llama-quantize --imatrix "/home/$user/AI/model/$model$suffix-GGUF/$model$suffix.imatrix" "/home/$user/AI/model/$model$suffix-GGUF/$model$suffix.F16.gguf" "/home/$user/AI/model/$model$suffix-GGUF/$model$suffix.$param.gguf" $param $(nproc)
    HF_HUB_ENABLE_HF_TRANSFER=1 huggingface-cli upload --repo-type model --commit-message "GGUF model commit (made with llama.cpp $llama_cpp_version)" "$model$suffix-GGUF" "/home/$user/AI/model/$model$suffix-GGUF/$model$suffix.$param.gguf"
    rm -rf "/home/$user/AI/model/$model$suffix-GGUF/$model$suffix.$param.gguf"
done

# クリーンアップ
rm -rf /home/$user/AI/model/$model$suffix-GGUF/

# ホームに戻る
cd /home/$user/

なぜログインが必要なのか?次のセクションで説明する(x

imatrix の部分でかなり苦労した(RTFM を忘れたせいで)、以前は imatrix が何なのか理解できず、一時的に AI の量子化を諦めていた。以前は環境準備でダウンロードしたcalibration_datav3.txtが直接llama-quantizeに使えると思っていたし、imatrix は I-Quant だけが使用するものだと思っていた(なぜならこのものの頭文字が I だから(強引な説明(難視)))。今回はしっかりと教訓を得た:imatrix は量子化の差を校正するための重要性マトリックス(Importance Matrix)であり、F16/F32/BF16 以外の量子化はすべてこれから恩恵を受けることができる(品質向上)。参考:Qwen Docs における llama.cpp の量子化に関する説明(しかしこの記事を書いているときにこの素晴らしいものを発見した、悲しい)。

ここでのコードではbartowski(AI 量子化の真の専門家)の規範に従い、彼のcalibration_datav3.txtデータセット(真の万能薬)を使用して imatrix を生成した。

また、個人的には imatrix を生成するためのデータセットが十分に広範囲で長ければ、重要性マトリックスの役割を果たすことができると考えている。もちろん、AI のトレーニングデータに近い校正データセットがあればより良いが、直接万能薬を使っても実際には大差ない(人間に見せるわけではないので、トークンをそのまま保存すれば、LLM がどんな逆天的な回答を出しても、最終的な校正はほぼ同じになるはずだ。インスピレーションの出所:Hackergame 2023 の小型大言語モデルの公式解答。もちろん私の考えにはデータの裏付けはないので、皆さんの指摘を歓迎します())

ここではT-Quantsも量子化したが、私がllama.cppの GitHub リポジトリから得た情報によると、T-Quantsはまだ初期段階にあり、現時点ではllama.cppmasterブランチに対して、T-Quantsは AVX2 加速の CPU に対して非常に良い効果を発揮し、他のものは一般的な性能を示す。GPU のサポートはまだ未合併の PR 段階にある。

白嫖の結末#

しかし、皆さんご存知の通り、mzwing は常に白嫖を愛しているので、今回の量子化は Huggingface Space で実行された(code-server を使用)、結果は予想通りの予想外:量子化の途中で space が自動的に再起動した…… そのため、私が以前に苦労して完成させた量子化が、ドン・ドン・ドン

そのため、上記のllama_cpp_quantize.shに各ステップが完了するたびに結果を自動的にアップロードするように追加し、resume_quantization.shを作成した(石山が石山を打ち負かす(乱雲))。

(何?私に白嫖を諦めさせる?不可能だ.webp)

現在の推測では、Huggingface Space でカスタム Dockerfile を使用し、長時間 CPU を高負荷にすると(量子化の際にllama-quantize$(nproc)を設定した(心虚))、space が自動的に再起動し、非永続的ストレージに保存された進捗が失われる。現時点ではresume_quantization.shを使えばなんとかなる。あまりにも石山で、皆がどう書くかを予想できるので、公開はしない(((

実験成果:

慣例の穴掘り#

ついに環境準備で掘った穴を埋めることができる!(x

現在、autoggufyというプロジェクトを作成し、自動量子化と自動的な量子化進捗の復元を実現する予定だが、AI が書いたv(-1)(何の neta v(x))は私が望む効果を達成できず、私にはそれを書く時間があまりない(悲)。

これで終わりだと思った?(x#

驚くな、私はまだ水を得ることができる!.jpg(x

第二部を検索しているときに、BBuf/RWKV-World-HF-Tokenizerを偶然発見した。本来は他の方法が見つからなかった場合、この少し草台班子の py スクリプトを使って rwkv6 を HF フォーマットに変換しようと思っていた(詳しくはそのREADME.mdを参照)、幸いにも btaskel 大佬が私に比較的完璧な解決策を提供してくれた(

しかし、このリポジトリには素晴らしいスクリプトがある!

# convert_rwkv5_to_6.py
# Original code from <https://github.com/BBuf/RWKV-World-HF-Tokenizer/blob/main/scripts/convert5to6.py>
import sys
import math
import torch
from collections import OrderedDict
import re

if len(sys.argv) != 3:
    print(f"RWKV5.2 pth (non-huggingface) チェックポイントをRWKV6.0に変換します")
    print("Usage: python convert5to6.py in_file out_file")
    exit()

model_path = sys.argv[1]

print("ファイルを読み込んでいます...")
state_dict = torch.load(model_path, map_location='cpu')

def convert_state_dict(state_dict):
    n_layer = 0
    n_embd = 0
    dim_att = 0

    state_dict_keys = list(state_dict.keys())
    for name in state_dict_keys:
        weight = state_dict.pop(name)

        # time_decayを(self.n_head, self.head_size)から(1,1,args.dim_att)に変換
        if '.att.time_decay' in name:
            weight = weight.view(1,1,weight.size(0)*weight.size(1))
            n_embd = dim_att = weight.size(-1) 
        # time_mix_k, v, r, gをtime_maaに変換(TimeMixとFFNの両方に)
        if '.time_mix_' in name:
            name = name[:-5] + 'maa_' + name[-1:]
            weight = 1.0 - weight

        if name.startswith('blocks.'):
            layer_id_match = re.search(r"blocks\.(\d+)\.att", name)
            if layer_id_match is not None:
                n_layer = max(n_layer, int(layer_id_match.group(1)) + 1)

        state_dict[name] = weight

    # 5.2にはない新しいパラメータを追加
    for layer_id in range(n_layer):
        layer_name = f'blocks.{layer_id}.att'

        ratio_0_to_1 = layer_id / (n_layer - 1)  # 0から1
        ratio_1_to_almost0 = 1.0 - (layer_id / n_layer)  # 1からほぼ0
        ddd = torch.ones(1, 1, n_embd)
        for i in range(n_embd):
            ddd[0, 0, i] = i / n_embd

        state_dict[layer_name + '.time_maa_x'] = (1.0 - torch.pow(ddd, ratio_1_to_almost0))
        state_dict[layer_name + '.time_maa_w'] = (1.0 - torch.pow(ddd, ratio_1_to_almost0))

        TIME_MIX_EXTRA_DIM = 32 # w,k,v,r,gのためのTIME_MIXを生成
        state_dict[layer_name + '.time_maa_w1'] = (torch.zeros(n_embd, TIME_MIX_EXTRA_DIM*5).uniform_(-1e-4, 1e-4))
        state_dict[layer_name + '.time_maa_w2'] = (torch.zeros(5, TIME_MIX_EXTRA_DIM, n_embd).uniform_(-1e-4, 1e-4))

        TIME_DECAY_EXTRA_DIM = 64
        state_dict[layer_name + '.time_decay_w1'] = (torch.zeros(n_embd, TIME_DECAY_EXTRA_DIM).uniform_(-1e-4, 1e-4))
        state_dict[layer_name + '.time_decay_w2'] = (torch.zeros(TIME_DECAY_EXTRA_DIM, dim_att).uniform_(-1e-4, 1e-4))

    print(f"n_layer: {n_layer}\nn_embd: {n_embd}")

    return state_dict

state_dict = convert_state_dict(state_dict)

torch.save(state_dict,sys.argv[2])
print("完了。ファイルが書き込まれました。")
# 元のモデルをダウンロード
aria2c -c -x16 "https://huggingface.co/$author/$model/resolve/main/$model$suffix.pth?download=true" -d /home/$user/AI/model/$model$suffix-original/ -o $model$suffix-5.pth

# RWKV5をRWKV6に変換
source /home/$user/AI/repo/rwkv/.venv/bin/activate
python /home/$user/convert_rwkv5_to_6.py /home/$user/AI/model/$model$suffix-original/$model$suffix-5.pth /home/$user/AI/model/$model$suffix-original/$model$suffix.pth
rm -rf /home/$user/AI/model/$model$suffix-original/$model$suffix-5.pth

驚くべきことに……RWKV 5.2 を RWKV 6.0 に変換できるとは……

私はすぐに実験を開始し、結果的に成功した?実験成果

後記#

とにかくこれが RWKV の奇妙な量子化方法だ…… 備忘録としても役立つ。今は高校三年生で、これをやっているのはかなりのプレッシャーだ。

また、この記事の最初は Deepseek に代わりにやってもらおうと思っていたが、予想通りの予想外(VSCode の自動補完がそう言った(x))Deepseek と私の文体の差が大きすぎて、やむを得ず自分でこの文章を完成させることにした。年に一度のブログ更新者への一つの説明でもある。

この記事に登場するコードと文章は同じオープンソースライセンスに従い(引用されたコード部分はそれぞれのコードリポジトリのオープンソースライセンスに従う)。

ここまで読んでくれてありがとう(

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。