File size: 6,480 Bytes
26d5b81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#!/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()