File size: 8,496 Bytes
fdc6474
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#!/usr/bin/env python3
"""Assemble the per-expert hybrid NVFP4+AQLM GLM-5.2 checkpoint.

Sources:
  - /tmp/glm52-dl/nvfp4-full: all non-expert tensors verbatim; per-expert
    NVFP4 tensors for the whole-NVFP4 layers AND for hot experts of hybrid
    layers (compacted into fused arrays)
  - /data/glm52-aqlm-parts/layer_N.pt: AQLM codes for base/cold experts
    (sliced per assignment; cold w2 = book-0 slice of the 2-book codes)
  - /data/glm52-expert-assignment.json: per-layer hot/cold expert ids

Output: /data/glm52-v3 (swap into /data/glm52 after validation).
"""

import json
import os
import shutil

import torch
from safetensors import safe_open
from safetensors.torch import save_file

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SRC = "/tmp/glm52-dl/nvfp4-full"
PARTS = "/data/glm52-aqlm-parts"
ASSIGN = "/data/glm52-expert-assignment.json"
DST = "/data/glm52-v3"
SHARD_BYTES = 4 << 30

plan = json.load(open(os.path.join(ROOT, "hybrid_plan.json")))
NVFP4_LAYERS = set(plan["nvfp4_layers"])           # whole-layer NVFP4 (incl MTP)
assignment = {int(k): v for k, v in json.load(open(ASSIGN)).items()}
HYBRID_LAYERS = sorted(assignment)

import re

_EXPERT_RE = re.compile(r"model\.layers\.(\d+)\.mlp\.experts\.(\d+)\.")


def keep(name: str) -> bool:
    m = _EXPERT_RE.match(name)
    if m:
        return int(m.group(1)) in NVFP4_LAYERS
    return True


class ShardWriter:
    def __init__(self, dst: str):
        self.dst = dst
        self.cur: dict[str, torch.Tensor] = {}
        self.cur_bytes = 0
        self.n = 0
        self.weight_map: dict[str, str] = {}
        self.total = 0
        self.files: list[str] = []

    def add(self, name: str, tensor: torch.Tensor):
        nb = tensor.numel() * tensor.element_size()
        if self.cur_bytes + nb > SHARD_BYTES and self.cur:
            self.flush()
        self.cur[name] = tensor
        self.cur_bytes += nb
        self.total += nb

    def flush(self):
        if not self.cur:
            return
        self.n += 1
        fname = f"model-{self.n:05d}.safetensors"
        save_file(self.cur, os.path.join(self.dst, fname))
        for k in self.cur:
            self.weight_map[k] = fname
        print(f"  wrote {fname} ({self.cur_bytes/1e9:.2f} GB)", flush=True)
        self.files.append(fname)
        self.cur = {}
        self.cur_bytes = 0

    def finalize(self):
        self.flush()
        total_n = self.n
        wm = {}
        for i, fname in enumerate(self.files, 1):
            new = f"model-{i:05d}-of-{total_n:05d}.safetensors"
            os.rename(os.path.join(self.dst, fname), os.path.join(self.dst, new))
            for k, v in self.weight_map.items():
                if v == fname:
                    wm[k] = new
        json.dump(
            {"metadata": {"total_size": self.total}, "weight_map": wm},
            open(os.path.join(self.dst, "model.safetensors.index.json"), "w"),
            indent=0,
        )
        print(f"index: {len(wm)} tensors, {self.total/1e9:.1f} GB total")


class ShardReader:
    def __init__(self, repo_dir: str):
        idx = json.load(open(os.path.join(repo_dir, "model.safetensors.index.json")))
        self.weight_map = idx["weight_map"]
        self.repo_dir = repo_dir
        self._open = {}

    def get(self, name: str):
        shard = self.weight_map[name]
        if shard not in self._open:
            self._open[shard] = safe_open(
                os.path.join(self.repo_dir, shard), framework="pt"
            )
        return self._open[shard].get_tensor(name)


def build_hybrid_layer(li: int, reader: ShardReader, writer: ShardWriter):
    n_exp, inter, hidden = 256, 2048, 6144
    hot = sorted(assignment[li]["hot"])
    cold = sorted(assignment[li]["cold"])
    hot_s, cold_s = set(hot), set(cold)
    base = [e for e in range(n_exp) if e not in hot_s and e not in cold_s]
    b_all = sorted(set(base) | cold_s)  # AQLM w13 group, ascending

    kind = torch.ones(n_exp, dtype=torch.int8)
    for e in hot:
        kind[e] = 0
    for e in cold:
        kind[e] = 2

    part = torch.load(
        os.path.join(PARTS, f"layer_{li}.pt"), map_location="cpu",
        weights_only=True,
    )
    assert part["books"]["w2"] == 2, f"layer {li} part lacks 2-book w2"
    p = f"model.layers.{li}.mlp.experts"

    b_idx = torch.tensor(b_all, dtype=torch.long)
    base_idx = torch.tensor(base, dtype=torch.long)
    cold_idx = torch.tensor(cold, dtype=torch.long)

    writer.add(f"{p}.hyb_kind", kind)
    writer.add(f"{p}.w13_codes", part["w13_codes"][b_idx].contiguous())
    writer.add(f"{p}.w13_codebooks", part["w13_codebooks"])
    writer.add(f"{p}.w13_scales", part["w13_scales"][b_idx].contiguous())
    writer.add(f"{p}.w2m_codes", part["w2_codes"][base_idx].contiguous())
    writer.add(f"{p}.w2m_codebooks", part["w2_codebooks"])
    writer.add(f"{p}.w2m_scales", part["w2_scales"][base_idx].contiguous())
    # cold: book-0 slice of the 2-book residual encoding
    writer.add(f"{p}.w2c_codes", part["w2_codes"][cold_idx, :1].clone())
    writer.add(f"{p}.w2c_codebooks", part["w2_codebooks"][:1].clone())
    writer.add(f"{p}.w2c_scales", part["w2_scales"][cold_idx].contiguous())

    # hot experts: compact NVFP4 arrays from the source repo
    na = len(hot)
    w13_packed = torch.empty(na, 2 * inter, hidden // 2, dtype=torch.uint8)
    w13_bscale = torch.empty(na, 2 * inter, hidden // 16, dtype=torch.uint8)
    w13_scale2 = torch.empty(na, 2, dtype=torch.float32)
    w2_packed = torch.empty(na, hidden, inter // 2, dtype=torch.uint8)
    w2_bscale = torch.empty(na, hidden, inter // 16, dtype=torch.uint8)
    w2_scale2 = torch.empty(na, 1, dtype=torch.float32)
    for j, e in enumerate(hot):
        ep = f"model.layers.{li}.mlp.experts.{e}"
        g_w = reader.get(f"{ep}.gate_proj.weight")
        u_w = reader.get(f"{ep}.up_proj.weight")
        d_w = reader.get(f"{ep}.down_proj.weight")
        w13_packed[j, :inter] = g_w
        w13_packed[j, inter:] = u_w
        w2_packed[j] = d_w
        w13_bscale[j, :inter] = reader.get(
            f"{ep}.gate_proj.weight_scale").view(torch.uint8)
        w13_bscale[j, inter:] = reader.get(
            f"{ep}.up_proj.weight_scale").view(torch.uint8)
        w2_bscale[j] = reader.get(
            f"{ep}.down_proj.weight_scale").view(torch.uint8)
        w13_scale2[j, 0] = reader.get(f"{ep}.gate_proj.weight_scale_2").float()
        w13_scale2[j, 1] = reader.get(f"{ep}.up_proj.weight_scale_2").float()
        w2_scale2[j, 0] = reader.get(f"{ep}.down_proj.weight_scale_2").float()
    writer.add(f"{p}.nvfp4_w13_packed", w13_packed)
    writer.add(f"{p}.nvfp4_w13_bscale", w13_bscale)
    writer.add(f"{p}.nvfp4_w13_scale2", w13_scale2)
    writer.add(f"{p}.nvfp4_w2_packed", w2_packed)
    writer.add(f"{p}.nvfp4_w2_bscale", w2_bscale)
    writer.add(f"{p}.nvfp4_w2_scale2", w2_scale2)

    print(f"layer {li}: hot={na} base={len(base)} cold={len(cold)}", flush=True)
    return {"n_nvfp4": na, "n_base": len(base), "n_cold": len(cold)}


def main():
    os.makedirs(DST, exist_ok=True)
    reader = ShardReader(SRC)
    writer = ShardWriter(DST)

    # 1. non-expert tensors + whole-NVFP4 layers, streamed shard by shard
    shards = sorted(set(reader.weight_map.values()))
    for shard in shards:
        with safe_open(os.path.join(SRC, shard), framework="pt") as f:
            names = [n for n in f.keys() if keep(n)]
            if not names:
                continue
            for n in names:
                writer.add(n, f.get_tensor(n))
        print(f"{shard}: kept {len(names)}", flush=True)

    # 2. hybrid layers
    layer_books = {}
    for li in HYBRID_LAYERS:
        layer_books[str(li)] = build_hybrid_layer(li, reader, writer)

    writer.finalize()

    # 3. config
    cfg = json.load(open(os.path.join(SRC, "config.json")))
    cfg["quantization_config"] = {
        "quant_method": "nvfp4_aqlm_hybrid",
        "nvfp4": cfg["quantization_config"],
        "aqlm": {"entries": 65536, "group_size": 8},
        "aqlm_layer_books": layer_books,
    }
    json.dump(cfg, open(os.path.join(DST, "config.json"), "w"), indent=2)

    # 4. aux files
    for f in os.listdir(SRC):
        if (
            f.endswith(".json")
            and f not in ("config.json", "model.safetensors.index.json")
            or f.endswith((".txt", ".jinja", ".py", ".md"))
        ):
            shutil.copy2(os.path.join(SRC, f), os.path.join(DST, f))
    print("DONE:", DST)


if __name__ == "__main__":
    main()