File size: 7,795 Bytes
e51b495
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Reading checkpoints and adapters, and reading/writing llama.cpp control-vector GGUFs.

Tensors are read straight from safetensors by byte offset, so a 60 GB checkpoint is never loaded whole:
the fit reads one layer at a time. bf16 is upcast to fp32 exactly (the bf16 bits are the high half of
the fp32 word).
"""
from __future__ import annotations

import json
import struct
from pathlib import Path

import numpy as np

PREFIXES = ("model.language_model.", "model.")   # multimodal exports, then text-only exports
N_LAYERS = 60


def _header(path: Path) -> tuple[int, dict]:
    with open(path, "rb") as fh:
        n = struct.unpack("<Q", fh.read(8))[0]
        return n, json.loads(fh.read(n))


_HEADERS: dict[str, tuple[int, dict]] = {}


def read_tensor(path: Path, name: str) -> np.ndarray:
    """One tensor from a safetensors file as fp32."""
    key = str(path)
    if key not in _HEADERS:
        _HEADERS[key] = _header(path)
    n, hd = _HEADERS[key]
    meta = hd[name]
    a, b = meta["data_offsets"]
    dtype = {"BF16": np.uint16, "F16": np.float16, "F32": np.float32}[meta["dtype"]]
    raw = np.fromfile(path, dtype=dtype, count=(b - a) // np.dtype(dtype).itemsize, offset=8 + n + a)
    raw = raw.reshape(meta["shape"])
    if meta["dtype"] == "BF16":
        return (raw.astype(np.uint32) << 16).view(np.float32)
    return raw.astype(np.float32)


def raw_bytes_equal(path_a: Path, name_a: str, path_b: Path, name_b: str, chunk: int = 1 << 26) -> bool:
    """Whether two stored tensors are bit-identical (same dtype, shape and bytes), read in chunks."""
    na, ha = _header(path_a)
    nb, hb = _header(path_b)
    ma, mb = ha[name_a], hb[name_b]
    if ma["dtype"] != mb["dtype"] or ma["shape"] != mb["shape"]:
        return False
    (a0, a1), (b0, _) = ma["data_offsets"], mb["data_offsets"]
    with open(path_a, "rb") as fa, open(path_b, "rb") as fb:
        fa.seek(8 + na + a0)
        fb.seek(8 + nb + b0)
        left = a1 - a0
        while left:
            k = min(chunk, left)
            if fa.read(k) != fb.read(k):
                return False
            left -= k
    return True


class Checkpoint:
    """Tensor access by name over one safetensors checkpoint directory (sharded or not)."""

    def __init__(self, directory: str | Path):
        self.dir = Path(directory)
        index = self.dir / "model.safetensors.index.json"
        if index.exists():
            self.map = json.loads(index.read_text())["weight_map"]
        else:
            single = self.dir / "model.safetensors"
            self.map = {k: single.name for k in _header(single)[1] if k != "__metadata__"}
        prefix = next((p for p in PREFIXES if any(k.startswith(p + "layers.") for k in self.map)), None)
        assert prefix, f"{self.dir}: no decoder layers found"
        self.prefix: str = prefix
        layers = {int(k[len(self.prefix) + 7:].split(".")[0]) for k in self.map if k.startswith(self.prefix + "layers.")}
        # the vision tower's blocks share module names with the language stack; count only this prefix
        assert layers == set(range(N_LAYERS)), f"{self.dir}: expected {N_LAYERS} decoder layers, found {len(layers)}"

    def file(self, name: str) -> Path:
        return self.dir / self.map[self.prefix + name]

    def has(self, name: str) -> bool:
        return self.prefix + name in self.map

    def get(self, name: str) -> np.ndarray:
        """name relative to the language-model prefix, e.g. 'layers.3.mlp.up_proj.weight'."""
        return read_tensor(self.file(name), self.prefix + name)

    def layer(self, L: int) -> dict[str, np.ndarray]:
        stem = f"{self.prefix}layers.{L}."
        return {k[len(stem):]: read_tensor(self.dir / f, k) for k, f in self.map.items() if k.startswith(stem)}

    def same_as(self, other: "Checkpoint", name: str) -> bool:
        return raw_bytes_equal(self.file(name), self.prefix + name, other.file(name), other.prefix + name)

    def embedding_rows(self, token_ids: np.ndarray) -> np.ndarray:
        """Rows of embed_tokens for the given ids as fp32, read row by row (only the rows used)."""
        name = self.prefix + "embed_tokens.weight"
        path = self.file("embed_tokens.weight")
        n, hd = _header(path)
        meta = hd[name]
        assert meta["dtype"] == "BF16", meta["dtype"]
        width = meta["shape"][1]
        start = 8 + n + meta["data_offsets"][0]
        table = {}
        with open(path, "rb") as fh:
            for t in np.unique(token_ids):
                fh.seek(start + int(t) * width * 2)
                table[int(t)] = (np.frombuffer(fh.read(width * 2), np.uint16).astype(np.uint32) << 16).view(np.float32)
        return np.stack([np.stack([table[int(t)] for t in row]) for row in token_ids])


class Adapter:
    """A PEFT LoRA adapter directory (adapter_config.json + adapter_model.safetensors)."""

    def __init__(self, directory: str | Path, base_prefix: str = "model.language_model."):
        self.dir = Path(directory)
        cfg = json.loads((self.dir / "adapter_config.json").read_text())
        assert not cfg.get("use_rslora") and not cfg.get("use_dora"), "rsLoRA / DoRA adapters are not handled"
        self.rank = int(cfg["r"])
        self.scale = cfg["lora_alpha"] / self.rank
        self.file = self.dir / "adapter_model.safetensors"
        self.keys = set(_header(self.file)[1])
        self.stem = f"base_model.model.{base_prefix}layers."

    def families(self, L: int) -> list[str]:
        """Projection names this adapter trains in layer L, e.g. 'self_attn.q_proj'."""
        stem = f"{self.stem}{L}."
        return sorted({k[len(stem):-len(".lora_A.weight")] for k in self.keys
                       if k.startswith(stem) and k.endswith(".lora_A.weight")})

    def pair(self, L: int, family: str) -> tuple[np.ndarray, np.ndarray]:
        """(A, scale * B) for one projection: the update is (scale * B) @ A."""
        k = f"{self.stem}{L}.{family}"
        A = read_tensor(self.file, f"{k}.lora_A.weight")
        B = read_tensor(self.file, f"{k}.lora_B.weight")
        return A, self.scale * B


# ---- llama.cpp control-vector GGUF ----------------------------------------------------------------------------
# One 1-D F32 tensor per layer named direction.<L>, L >= 1 (llama.cpp never applies a vector at layer 0), and
# the metadata cvector-generator writes (common/common.cpp, common_control_vector_load_one).

def write_gguf(path: str | Path, vectors: np.ndarray, name: str | None = None, alpha: float = 1.0) -> None:
    """Write alpha * vectors[1:] as a control-vector GGUF, so it loads at strength 1.0."""
    from gguf import GGUFWriter

    vectors = np.asarray(vectors, np.float32)
    assert vectors.shape[0] == N_LAYERS, vectors.shape
    w = GGUFWriter(str(path), arch="controlvector")
    w.add_string("controlvector.model_hint", "gemma4")
    w.add_int32("controlvector.layer_count", N_LAYERS - 1)
    w.add_float32("controlvector.alpha", float(alpha))
    if name:
        w.add_string("general.name", name)
    for L in range(1, N_LAYERS):
        w.add_tensor(f"direction.{L}", np.ascontiguousarray(alpha * vectors[L], np.float32))
    w.write_header_to_file()
    w.write_kv_data_to_file()
    w.write_tensors_to_file()
    w.close()


def read_gguf(path: str | Path) -> np.ndarray:
    """A control-vector GGUF's direction.L tensors as an (N_LAYERS, d) array; layer 0 stays zero."""
    from gguf import GGUFReader

    r = GGUFReader(str(path))
    t = {x.name: np.asarray(x.data, np.float32) for x in r.tensors if x.name.startswith("direction.")}
    assert set(t) == {f"direction.{L}" for L in range(1, N_LAYERS)}, (path, len(t))
    out = np.zeros((N_LAYERS, len(t["direction.1"])), np.float32)
    for k, v in t.items():
        out[int(k.split(".")[1])] = v
    return out