File size: 8,825 Bytes
51f2415
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""MXFP8 re-encoding of the trial-117 bake's BF16 attention output projections (qwen50, 2026-10-21).

Format matches the NVIDIA base for these layers: E4M3 codes [N, K] plus one E8M0 scale byte per
32 input elements, exponent = ceil(log2(amax / 448)) (the base's rule for ~95% of blocks; the rest
differ only because the base derived amax before quantization).

Constrained rounding: every element takes one of its two nearest E4M3 neighbours on the unchanged
block scale (never more than one step from the BF16 value). Starting from round-to-nearest, a
greedy pass flips elements to cancel, per input column, the error along the layer's removed output
direction r (the left singular vector of bake - base, which is rank-1). This keeps r^T Q(W') close
to r^T W', so the abliteration survives the re-encoding.

Usage (in the serving image, CPU):
  python3 mxfp8_reencode.py report <layers...>        # fidelity for a few layers, no files written
  python3 mxfp8_reencode.py build /out                 # write rewritten shards + metadata to /out;
                                                        # unchanged shards are listed for host-side hard links
Mounts: /base (NVIDIA FP4-Local), /bake (trial-117 bake), /out (new checkpoint dir, empty).
"""
import json
import os
import re
import shutil
import sys
import time

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

BASE, BAKE = '/base', '/bake'
IB = json.load(open(f'{BASE}/model.safetensors.index.json'))['weight_map']
IK = json.load(open(f'{BAKE}/model.safetensors.index.json'))
WK = IK['weight_map']
TARGET = re.compile(r'^model\.language_model\.layers\.(\d+)\.(linear_attn\.out_proj|self_attn\.o_proj)$')
ITERS = 1024


def tensor(root, idx, name):
    with safe_open(f'{root}/{idx[name]}', 'pt', device='cpu') as f:
        return f.get_tensor(name)


def base_dequant(n):
    w = tensor(BASE, IB, n + '.weight').float()
    e = tensor(BASE, IB, n + '.weight_scale').to(torch.int32) - 127
    return (w.view(w.shape[0], -1, 32) * torch.pow(2.0, e.float())[..., None]).view(w.shape)


def removed_direction(n, wp):
    d = wp - base_dequant(n)
    u, s, _ = torch.linalg.svd(d, full_matrices=False)
    return u[:, 0], (s[1] / s[0]).item() if s.numel() > 1 else 0.0


def encode(wp, r):
    """Return (codes fp8 [N,K], scale uint8 [N,K/32], dequantized float [N,K]) with constrained rounding."""
    n_rows, k = wp.shape
    blocks = wp.view(n_rows, -1, 32)
    amax = blocks.abs().amax(-1, keepdim=True).clamp_min(2.0 ** -126)
    exp = torch.ceil(torch.log2(amax / 448.0)).clamp(-127, 127)
    scale = torch.pow(2.0, exp)
    x = (blocks / scale).view(n_rows, k)                       # scaled targets, |x| <= 448
    sc = scale.expand(-1, -1, 32).reshape(n_rows, k)
    q8 = x.to(torch.float8_e4m3fn)
    bits = q8.view(torch.uint8).to(torch.int16)
    qf = q8.float()
    # the other neighbour: one E4M3 step from q towards x (magnitude up or down)
    mag, sign = bits & 0x7F, bits & 0x80
    toward_up = (x.abs() > qf.abs()) | ((qf == 0) & (x != 0))
    alt_mag = torch.where(toward_up, (mag + 1).clamp(max=0x7E), (mag - 1).clamp(min=0))
    alt_sign = torch.where((qf == 0) & (x < 0), torch.full_like(sign, 0x80), sign)
    alt_bits = (alt_sign | alt_mag).to(torch.uint8)
    altf = alt_bits.view(torch.float8_e4m3fn).float()
    valid = (alt_bits != bits.to(torch.uint8)) & (alt_mag != mag)
    # error contributions along r, per column, in original units
    err = (qf - x) * sc                                        # current error per element
    delta = (altf - qf) * sc                                   # change if flipped
    rv = r[:, None]
    col = (rv * err).sum(0)                                    # r^T E per column
    gain_unit = rv * delta                                     # change in r^T E per flip
    flipped = torch.zeros_like(valid)
    added = (err + delta) ** 2 - err ** 2                      # extra squared error if flipped (>= 0)
    tol = 1e-4 * col.abs().max().clamp_min(1e-12)
    for _ in range(ITERS):
        # per column, the flip with the best |r^T E| reduction per unit of added squared error
        new_abs = (col[None, :] + gain_unit).abs()
        reduction = col.abs()[None, :] - new_abs
        ok = valid & ~flipped & (reduction > 0)
        score = torch.where(ok, reduction / added.clamp_min(1e-30), torch.full_like(reduction, -1.0))
        best, idx = score.max(0)
        take = (best > 0) & (col.abs() > tol)
        if not take.any():
            break
        cols = torch.nonzero(take).squeeze(1)
        rows = idx[cols]
        flipped[rows, cols] = True
        col[cols] += gain_unit[rows, cols]
    fidelity.last_flips = flipped.float().mean().item()
    codes = torch.where(flipped, alt_bits, bits.to(torch.uint8)).view(torch.float8_e4m3fn)
    deq = codes.float() * sc
    scale_u8 = (exp.squeeze(-1) + 127).to(torch.uint8)
    return codes, scale_u8, deq


def fidelity(n):
    wp = tensor(BAKE, WK, n + '.weight').float()
    r, ratio = removed_direction(n, wp)
    w0 = base_dequant(n)
    codes, scale, deq = encode(wp, r)
    e = deq - wp
    keep_base = (r @ w0).norm()
    return {'layer': n, 'rank1_ratio_s2_s1': round(ratio, 5),
            'removed_fraction_bake': round(1 - ((r @ wp).norm() / keep_base).item(), 5),
            'removed_fraction_reencoded': round(1 - ((r @ deq).norm() / keep_base).item(), 5),
            'dir_err_over_intervention': round(((r @ e).norm() / (r @ (wp - w0)).norm()).item(), 5),
            'rel_err': round((e.norm() / wp.norm()).item(), 5),
            'flipped_fraction': round(fidelity.last_flips, 6)}, codes, scale


def report(layers):
    for layer in layers:
        for kind in ('linear_attn.out_proj', 'self_attn.o_proj'):
            n = f'model.language_model.layers.{layer}.{kind}'
            if n + '.weight' in WK:
                t = time.time(); f, _, _ = fidelity(n); f['seconds'] = round(time.time() - t, 1); print(json.dumps(f), flush=True)


def build(out):
    assert os.path.isdir(out) and not os.listdir(out), 'output dir must exist and be empty'
    targets = sorted({k[:-len('.weight')] for k in WK if k.endswith('.weight') and TARGET.match(k[:-len('.weight')])})
    assert len(targets) == 48, len(targets)
    by_shard = {}
    for n in targets:
        by_shard.setdefault(WK[n + '.weight'], []).append(n)
    reports, new_map, links = [], dict(WK), []
    os.makedirs(f'{out}/tensors', exist_ok=True)
    for shard in sorted(set(WK.values())):
        src, dst = f'{BAKE}/{shard}', f'{out}/{shard}'
        if shard not in by_shard:
            links.append(shard)                                # unchanged: hard-linked on the host afterwards
            continue
        with safe_open(src, 'pt', device='cpu') as f:
            meta = f.metadata()
            tensors = {k: f.get_tensor(k) for k in f.keys()}
        for n in by_shard[shard]:
            rep, codes, scale = fidelity(n)
            tensors[n + '.weight'] = codes.contiguous()
            tensors[n + '.weight_scale'] = scale.contiguous()
            new_map[n + '.weight_scale'] = shard
            reports.append(rep); print(json.dumps(rep), flush=True)
        save_file(tensors, dst, metadata=meta)
        del tensors
    for name in os.listdir(BAKE):                                # small files: copy
        p = f'{BAKE}/{name}'
        if os.path.isfile(p) and name not in ('model.safetensors.index.json', 'config.json', 'SHA256SUMS', 'README.md'):
            shutil.copy2(p, f'{out}/{name}')
    idx = dict(IK); idx['weight_map'] = dict(sorted(new_map.items()))
    json.dump(idx, open(f'{out}/model.safetensors.index.json', 'w'), indent=2)
    cfg = json.load(open(f'{BAKE}/config.json')); base_cfg = json.load(open(f'{BASE}/config.json'))
    q, bq = cfg['quantization_config'], base_cfg['quantization_config']
    for n in targets:
        q['quantized_layers'][n] = bq['quantized_layers'][n]
    q['ignore'] = [i for i in q['ignore'] if i not in set(targets)]
    cfg['quantization_config'] = q
    cfg['qwen50_reencode'] = {'attention_output_projections': 'MXFP8 (base format), constrained rounding along each layer removed direction',
                              'source_revision': '57f88b00e153fedbc10843ba0cd6284d4c2b5c6c', 'layers': len(targets)}
    json.dump(cfg, open(f'{out}/config.json', 'w'), indent=2)
    json.dump(reports, open(f'{out}/reencode-report.json', 'w'), indent=1)
    json.dump(links, open(f'{out}/link-unchanged-shards.json', 'w'), indent=1)
    print(json.dumps({'event': 'BUILT', 'layers': len(reports), 'rewritten_shards': len(by_shard)}))


if __name__ == '__main__':
    torch.set_num_threads(min(8, os.cpu_count() or 8))
    if sys.argv[1] == 'report':
        report([int(x) for x in sys.argv[2:]])
    elif sys.argv[1] == 'build':
        build(sys.argv[2])