neuroflow-cpp / scripts /lmh2_to_lmh1.py
cwenzi's picture
Upload folder using huggingface_hub
26d5b81 verified
Raw
History Blame Contribute Delete
6.48 kB
#!/usr/bin/env python3
"""
LMH2 → LMH1 转换器 / LMH1 生成器
模式1 (有lm_head.nfv1): 从 C++ LMH2 提取 w_embed/w_proj,新建 bridge
模式2 (无lm_head.nfv1): 从 model.nfv1 读取维度,全部 xavier 初始化
LMH1 张量: bridge.weight, bridge.bias, w_proj.weight, w_proj.bias, w_embed
用法:
# 模式1: 从LMH2转换
python3 scripts/lmh2_to_lmh1.py \
--nf-model output/model.nfv1 \
--lmh2 output/lm_head.nfv1 \
--output lm_head_lmh1.nfv1
# 模式2: 从model.nfv1直接生成 (无需lm_head)
python3 scripts/lmh2_to_lmh1.py \
--nf-model output/model.nfv1 \
--vocab-size 128000 \
--d-model 512 \
--output lm_head_lmh1.nfv1
"""
import argparse, struct, sys, os
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from infer_full import load_nfv1, load_lmh1
def load_lmh2(path):
weights = {}
with open(path, 'rb') as f:
magic = f.read(4)
if magic not in (b'LMH2', b'LMH1'):
raise ValueError(f"Bad magic: {magic} (expected LMH2 or LMH1)")
while True:
nl = struct.unpack('<I', f.read(4))[0]
if nl == 0:
break
name = f.read(nl).decode('utf-8', errors='replace')
ndim = struct.unpack('<I', f.read(4))[0]
shape = tuple(struct.unpack('<I', f.read(4))[0] for _ in range(ndim))
dsize = struct.unpack('<I', f.read(4))[0]
arr = np.frombuffer(f.read(dsize), dtype=np.float32).reshape(shape).copy()
weights[name] = arr
return weights, magic.decode('utf-8')
def save_lmh1(path, weights):
with open(path, 'wb') as f:
f.write(b'LMH1')
for name, arr in weights.items():
name_bytes = name.encode('utf-8')
f.write(struct.pack('<I', len(name_bytes)))
f.write(name_bytes)
f.write(struct.pack('<I', len(arr.shape)))
for d in arr.shape:
f.write(struct.pack('<I', d))
data = arr.astype(np.float32).tobytes()
f.write(struct.pack('<I', len(data)))
f.write(data)
f.write(struct.pack('<I', 0))
def xavier_uniform(shape, rng):
fan_in = shape[1] if len(shape) == 2 else shape[0]
fan_out = shape[0] if len(shape) == 2 else 1
limit = np.sqrt(6.0 / (fan_in + fan_out))
return rng.uniform(-limit, limit, size=shape).astype(np.float32)
def main():
parser = argparse.ArgumentParser(description='LMH2 → LMH1 converter / LMH1 generator')
parser.add_argument('--nf-model', required=True, help='NF 模型文件 (用于获取 hidden_dim)')
parser.add_argument('--lmh2', default='', help='C++ LMH2 格式 LM head 文件 (可选)')
parser.add_argument('--output', required=True, help='输出 LMH1 文件路径')
parser.add_argument('--vocab-size', type=int, default=128000, help='词表大小 (模式2需要)')
parser.add_argument('--d-model', type=int, default=0, help='LM d_model (0=从NF模型推断)')
parser.add_argument('--seed', type=int, default=42, help='随机种子')
args = parser.parse_args()
nf_w = load_nfv1(args.nf_model)
hidden_dim = nf_w['input_proj.weight'].shape[0]
print(f"NF hidden_dim: {hidden_dim}")
rng = np.random.RandomState(args.seed)
lmh1_w = {}
if args.lmh2 and os.path.exists(args.lmh2):
lmh2_w, fmt = load_lmh2(args.lmh2)
print(f"模式1: 从 {args.lmh2} 转换 (格式: {fmt}, {len(lmh2_w)} 个张量)")
if fmt == 'LMH1':
print("已经是 LMH1 格式,直接复制")
import shutil
shutil.copy2(args.lmh2, args.output)
print(f"输出: {args.output}")
return
vocab_size, d_model = lmh2_w['w_embed'].shape
print(f"vocab_size: {vocab_size}, d_model: {d_model}")
lmh1_w['w_embed'] = lmh2_w['w_embed']
print(f" w_embed: {lmh1_w['w_embed'].shape} (from LMH2)")
if 'w_proj.weight' in lmh2_w:
lmh1_w['w_proj.weight'] = lmh2_w['w_proj.weight']
lmh1_w['w_proj.bias'] = lmh2_w['w_proj.bias']
print(f" w_proj.weight: {lmh1_w['w_proj.weight'].shape} (from LMH2)")
print(f" w_proj.bias: {lmh1_w['w_proj.bias'].shape} (from LMH2)")
else:
lmh1_w['w_proj.weight'] = xavier_uniform((d_model, d_model), rng)
lmh1_w['w_proj.bias'] = np.zeros(d_model, dtype=np.float32)
print(f" w_proj.weight: {lmh1_w['w_proj.weight'].shape} (xavier init)")
print(f" w_proj.bias: {lmh1_w['w_proj.bias'].shape}")
else:
if args.lmh2:
print(f"警告: --lmh2 文件不存在: {args.lmh2}")
print(f"模式2: 从 model.nfv1 生成全新 LMH1")
d_model = args.d_model
if d_model == 0:
d_model = int(nf_w['output_fusion.up.weight'].shape[0])
print(f" d_model 从NF模型推断: {d_model}")
vocab_size = args.vocab_size
print(f"vocab_size: {vocab_size}, d_model: {d_model}")
lmh1_w['w_embed'] = xavier_uniform((vocab_size, d_model), rng)
print(f" w_embed: {lmh1_w['w_embed'].shape} (xavier init)")
lmh1_w['w_proj.weight'] = xavier_uniform((d_model, d_model), rng)
lmh1_w['w_proj.bias'] = np.zeros(d_model, dtype=np.float32)
print(f" w_proj.weight: {lmh1_w['w_proj.weight'].shape} (xavier init)")
print(f" w_proj.bias: {lmh1_w['w_proj.bias'].shape}")
lmh1_w['bridge.weight'] = xavier_uniform((d_model, hidden_dim), rng)
lmh1_w['bridge.bias'] = np.zeros(d_model, dtype=np.float32)
print(f" bridge.weight: {lmh1_w['bridge.weight'].shape} (xavier init)")
print(f" bridge.bias: {lmh1_w['bridge.bias'].shape}")
out_dir = os.path.dirname(os.path.abspath(args.output))
if out_dir:
os.makedirs(out_dir, exist_ok=True)
save_lmh1(args.output, lmh1_w)
print(f"\n输出: {args.output} (LMH1, {len(lmh1_w)} 个张量)")
verify_w = load_lmh1(args.output)
print(f"验证: {len(verify_w)} 个张量加载成功")
for name in lmh1_w:
if name in verify_w:
match = np.allclose(lmh1_w[name], verify_w[name])
print(f" {name}: {'OK' if match else 'MISMATCH'}")
else:
print(f" {name}: MISSING")
if __name__ == '__main__':
main()