環境準備喵#
我水,就硬水(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 repo
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
這個 command 的(惱,屑rye
然後…… 差點就沒有然後了#
接下來遇到的問題堪稱重量級…… 搜索了很久也找不到怎麼把 RWKV 轉換成 Huggingface Format 或是直接從 pth 轉換為 gguf。明明 RWKV 官方和 Transformers 一塊推出過一個轉換腳本,結果這腳本居然運行不了(也是服了,什麼草台班子)。這下算是陷入困境了,找了大概兩三天,都快放棄了。
甚至一度想過讓 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"Convert RWKV6.0 pth (non-huggingface) checkpoint to Huggingface format")
print("Usage: python convert_rwkv6_to_hf.py SOURCE_MODEL TARGET_MODEL")
exit()
SOURCE_MODEL = sys.argv[1]
TARGET_MODEL = sys.argv[2]
# delete target model
import os
if os.path.exists(TARGET_MODEL):
os.remove(TARGET_MODEL)
model = torch.load(SOURCE_MODEL, mmap=True, map_location="cpu")
# Rename all the keys, to include "rwkv."
new_model = {}
for key in model.keys():
# If the keys start with "blocks"
if key.startswith("blocks."):
new_key = "rwkv." + key
# Replace .att. with .attention.
new_key = new_key.replace(".att.", ".attention.")
# Replace .ffn. with .feed_forward.
new_key = new_key.replace(".ffn.", ".feed_forward.")
# Replace `0.ln0.` with `0.pre_ln.`
new_key = new_key.replace("0.ln0.", "0.pre_ln.")
else:
# No rename needed
new_key = key
# Rename `emb.weight` to `rwkv.embeddings.weight`
if key == "emb.weight":
new_key = "rwkv.embeddings.weight"
# Rename the `ln_out.x` to `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]
# Save the new model
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/
author
和model
是用來控制要量化的模型的,suffix
則是用來控制像Seikaijyu/RWKV6-7B-v3-porn-chat下的RWKV6-7B-v3-porn-chat-pro.pth
那種一個倉庫下面放一些變體的情況。
size
對應的是你要轉換的模型的參數量,比如1B6
之類(確切地說是基底模型的,然而一般而言偏差都不會太大的啦(x
鏈接那裡自己改改罷,我石山不想碰(x
此處邪典可以說是點滿了…… 我真的萬萬想不到居然這麼玩,直接 clone 原模型的 config 下來然後替換pytorch_model.bin
就完成了 HF 格式化……
另外記得即使是 RWKV v6 world 系列模型也請使用 RWKV 普通模型的 HF config,否則下面的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 "Generating 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 "Converting to $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/
為什麼要 login 呢?下一節談(x
imatrix 那裡折騰了我好久(忘記 RTFM 導致的),我此前就是因為沒搞明白 imatrix 才暫時退坑 AI 量化的。我此前以為環境準備那裡下載的calibration_datav3.txt
能直接給llama-quantize
用,而且我一直以為只有I-Quants
才會用到imatrix
(誰讓這玩意首字母都是 I 呢(強行解釋(難視)))。這次算是被好好上了一課了:imatrix
是用來校準量化差的重要性矩陣
(Importance Matrix
),除了F16
/F32
/BF16
之外的量化都能從中受益(提高質量)。可以參考:Qwen Docs 中關於 llama.cpp 量化的描述(然而我在寫這篇文章的時候才發現這個好東西,悲)。
此處的代碼中我跟隨bartowski(AI 量化真佬)的規範,用了他的calibration_datav3.txt
數據集(真・萬金油)進行imatrix
生成。
另外提一嘴,個人認為只要生成imatrix
用的數據集涵蓋的方面夠全、夠長,就可以實現重要性矩陣的作用。當然更貼近 AI 訓練數據的校準數據集當然是更好的,但是直接用萬金油效果其實也大差不差(因為又不是給人看的,直接保存 token,那無論 LLM 輸出了什麼逆天回答,最終校準也應該是差不多的。靈感來源:Hackergame 2023 小型大語言模型星球的官方題解。當然我的想法沒有任何數據支撐,歡迎各位大佬指出我的錯誤())
此處我還量化了T-Quants
,然而根據我搜索llama.cpp
的 GitHub repo 得到的資料,T-Quants
仍然處於早期階段,暫時對llama.cpp
的master
分支而言,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
也能用。由於過於石山且大家都能猜到怎麼寫的,就不放出來了(((
實驗成果:
- https://huggingface.co/mzwing/RWKV6-3B-Chn-UnlimitedRP-mini-chat-GGUF
- https://huggingface.co/mzwing/RWKV6-7B-v3-porn-chat-GGUF
- https://huggingface.co/mzwing/RWKV6-7B-v3-porn-chat-pro-GGUF
惯例的挖坑#
總算可以填掉環境準備那裡挖的坑力!(x
目前打算做一個名為autoggufy
的項目實現自動量化 + 自動恢復量化進度,然而 AI 寫的v(-1)
(什麼 neta v0(x)並不能達到我想要的效果,我又沒有那麼多時間自己寫(悲)
你以為這就結束了?(x#
沒想到吧,我還能水!.jpg(x
在第二部分搜索的時候意外發現了BBuf/RWKV-World-HF-Tokenizer,本來是打算搜不到別的辦法的話就用這個有點草台班子的 py 小腳本將 rwkv6 轉換為 HF Format 算了(詳見其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"Converts RWKV5.2 pth (non-huggingface) checkpoint to RWKV6.0")
print("Usage: python convert5to6.py in_file out_file")
exit()
model_path = sys.argv[1]
print("Loading file...")
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)
# convert time_decay from (self.n_head, self.head_size) to (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)
# convert time_mix_k, v, r, g into time_maa for both TimeMix and 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
# add in new params not in 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 to 1
ratio_1_to_almost0 = 1.0 - (layer_id / n_layer) # 1 to ~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 # generate TIME_MIX for w,k,v,r,g
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("DONE. File written.")
# 下載原始模型
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 與我的文風差距過大,不得已決定自己動手完成這篇文章,也算是對年更博主的一個交代吧(
本文出現的代碼與文章遵循同樣的開源協議(引用的代碼部分則遵循其代碼庫的開源協議)。
謝謝你看到這裡(