AQIT
0.1.0
Toggle main menu visibility
Loading...
Searching...
No Matches
publish_public_sae.py
Go to the documentation of this file.
1
# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2
"""Upload SAE + norm + UMAP to R2 and register in Supabase public_saes (no Node required)."""
3
4
from
__future__
import
annotations
5
6
import
json
7
import
os
8
import
sys
9
from
pathlib
import
Path
10
from
typing
import
Any
11
12
13
def
_load_env_files
() -> None:
14
roots = [
15
Path.cwd(),
16
Path.cwd().parent,
17
Path(__file__).resolve().parents[2],
18
]
19
seen: set[Path] = set()
20
for
root
in
roots:
21
for
name
in
(
".env.local"
,
".env"
):
22
path = (root / name).resolve()
23
if
path
in
seen
or
not
path.is_file():
24
continue
25
seen.add(path)
26
for
line
in
path.read_text(encoding=
"utf-8"
).splitlines():
27
line = line.strip()
28
if
not
line
or
line.startswith(
"#"
)
or
"="
not
in
line:
29
continue
30
key, val = line.split(
"="
, 1)
31
key = key.strip()
32
val = val.strip().strip(
'"'
).strip(
"'"
)
33
os.environ.setdefault(key, val)
34
35
36
def
_parse_flag
(args: list[str], name: str) -> str |
None
:
37
for
i, a
in
enumerate(args):
38
if
a == name
and
i + 1 < len(args):
39
return
args[i + 1]
40
return
None
41
42
43
def
_parse_num
(args: list[str], name: str, fallback: float | int |
None
=
None
) -> float | int |
None
:
44
raw =
_parse_flag
(args, name)
45
if
raw
is
None
or
raw ==
""
:
46
return
fallback
47
try
:
48
n = float(raw)
49
return
int(n)
if
n == int(n)
else
n
50
except
ValueError:
51
return
fallback
52
53
54
def
_read_json
(path: Path) -> dict[str, Any] |
None
:
55
if
not
path.is_file():
56
return
None
57
try
:
58
return
json.loads(path.read_text(encoding=
"utf-8"
))
59
except
Exception:
60
return
None
61
62
63
def
_r2_client
():
64
try
:
65
import
boto3
66
except
ImportError
as
exc:
67
raise
RuntimeError(
"pip install boto3"
)
from
exc
68
69
endpoint = os.environ.get(
"R2_ENDPOINT"
)
70
access_key = os.environ.get(
"R2_ACCESS_KEY_ID"
)
71
secret = os.environ.get(
"R2_SECRET_ACCESS_KEY"
)
72
missing = [
73
name
74
for
name, val
in
(
75
(
"R2_ENDPOINT"
, endpoint),
76
(
"R2_ACCESS_KEY_ID"
, access_key),
77
(
"R2_SECRET_ACCESS_KEY"
, secret),
78
(
"R2_BUCKET_NAME"
, os.environ.get(
"R2_BUCKET_NAME"
)),
79
)
80
if
not
val
81
]
82
if
missing:
83
raise
RuntimeError(f
"Missing env: {', '.join(missing)} (set in .env.local)"
)
84
85
return
boto3.client(
86
"s3"
,
87
endpoint_url=endpoint,
88
aws_access_key_id=access_key,
89
aws_secret_access_key=secret,
90
region_name=
"auto"
,
91
)
92
93
94
def
_upload_r2
(client: Any, bucket: str, key: str, file_path: Path, content_type: str) ->
None
:
95
body = file_path.read_bytes()
96
client.put_object(Bucket=bucket, Key=key, Body=body, ContentType=content_type)
97
print(f
" uploaded {key} ({len(body) / 1_000_000:.2f} MB)"
, flush=
True
)
98
99
100
def
_supabase_upsert
(row: dict[str, Any]) -> dict[str, Any]:
101
try
:
102
import
requests
103
except
ImportError
as
exc:
104
raise
RuntimeError(
"pip install requests"
)
from
exc
105
106
url = os.environ.get(
"NEXT_PUBLIC_SUPABASE_URL"
)
or
os.environ.get(
"SUPABASE_URL"
)
107
key = os.environ.get(
"SUPABASE_SERVICE_ROLE_KEY"
)
108
if
not
url
or
not
key:
109
raise
RuntimeError(
"Set NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in .env.local"
)
110
111
base = url.rstrip(
"/"
)
112
headers = {
113
"apikey"
: key,
114
"Authorization"
: f
"Bearer {key}"
,
115
"Content-Type"
:
"application/json"
,
116
"Prefer"
:
"resolution=merge-duplicates,return=representation"
,
117
}
118
resp = requests.post(
119
f
"{base}/rest/v1/public_saes?on_conflict=model_slug,layer"
,
120
headers=headers,
121
json=row,
122
timeout=60,
123
)
124
if
resp.status_code
in
(200, 201):
125
data = resp.json()
126
return
data[0]
if
isinstance(data, list)
and
data
else
data
127
128
# Fallback: plain insert
129
resp2 = requests.post(
130
f
"{base}/rest/v1/public_saes"
,
131
headers={**headers,
"Prefer"
:
"return=representation"
},
132
json=row,
133
timeout=60,
134
)
135
if
resp2.status_code
not
in
(200, 201):
136
raise
RuntimeError(f
"Supabase error {resp.status_code}: {resp.text}; insert: {resp2.text}"
)
137
data = resp2.json()
138
return
data[0]
if
isinstance(data, list)
and
data
else
data
139
140
141
def
publish_public_sae(args: list[str]) ->
None
:
142
_load_env_files
()
143
144
sae_path_s =
_parse_flag
(args,
"--sae"
)
145
if
not
sae_path_s:
146
print(
147
"Usage: aquin sae publish --sae <path.pt> --model-slug <slug> "
148
"[--layer N] [--model-name ...] [--d-model 1024] [--d-sae 32768]"
,
149
file=sys.stderr,
150
)
151
sys.exit(1)
152
153
sae_abs = Path(sae_path_s).expanduser().resolve()
154
if
not
sae_abs.is_file():
155
print(f
"SAE not found: {sae_abs}"
, file=sys.stderr)
156
sys.exit(1)
157
158
meta =
_read_json
(sae_abs.with_suffix(
".meta.json"
))
or
{}
159
model_slug =
_parse_flag
(args,
"--model-slug"
)
or
str(meta.get(
"model_id"
)
or
""
).strip()
160
if
not
model_slug:
161
print(
"Pass --model-slug or ensure .meta.json has model_id"
, file=sys.stderr)
162
sys.exit(1)
163
164
layer_raw =
_parse_num
(args,
"--layer"
)
or
meta.get(
"layer"
)
165
if
layer_raw
is
None
:
166
print(
"Pass --layer or ensure .meta.json has layer"
, file=sys.stderr)
167
sys.exit(1)
168
layer = int(layer_raw)
169
170
d_model = int(
_parse_num
(args,
"--d-model"
, meta.get(
"d_model"
)
or
2048)
or
2048)
171
d_sae = int(
_parse_num
(args,
"--d-sae"
, meta.get(
"n_features"
)
or
32768)
or
32768)
172
train_steps =
_parse_num
(args,
"--train-steps"
, meta.get(
"steps"
))
173
l0 =
_parse_num
(args,
"--l0"
)
174
dead_pct =
_parse_num
(args,
"--dead-pct"
)
175
interp_score =
_parse_num
(args,
"--interp-score"
)
176
model_name =
_parse_flag
(args,
"--model-name"
)
or
model_slug
177
arch =
_parse_flag
(args,
"--arch"
)
or
"sparse_autoencoder"
178
k = int(
_parse_num
(args,
"--k"
)
or
round(d_sae / d_model))
179
180
metrics =
_read_json
(sae_abs.with_suffix(
".metrics.json"
))
181
if
metrics
and
interp_score
is
None
and
metrics.get(
"interp_score"
)
is
not
None
:
182
interp_score = float(metrics[
"interp_score"
])
183
print(f
" metrics sidecar: {sae_abs.with_suffix('.metrics.json').name}"
, flush=
True
)
184
185
umap_path_s =
_parse_flag
(args,
"--umap"
)
186
umap_path = Path(umap_path_s).expanduser()
if
umap_path_s
else
sae_abs.with_suffix(
".umap.json"
)
187
if
not
umap_path.is_file():
188
umap_path =
None
# type: ignore[assignment]
189
190
norm_path_s =
_parse_flag
(args,
"--norm"
)
191
if
norm_path_s:
192
norm_path = Path(norm_path_s).expanduser()
193
else
:
194
parent = sae_abs.parent
195
candidates = [
196
parent / f
"norm_layer{layer}.pt"
,
197
parent / f
"_acts_layer{layer}"
/
"norm.pt"
,
198
parent /
"norm.pt"
,
199
]
200
norm_path = next((p
for
p
in
candidates
if
p.is_file()),
None
)
201
if
norm_path
is
None
or
not
norm_path.is_file():
202
print(
"Norm file not found. Pass --norm or train with aquin sae train."
, file=sys.stderr)
203
sys.exit(1)
204
205
bucket = os.environ[
"R2_BUCKET_NAME"
]
206
r2_sae_key = f
"saes/{model_slug}/l{layer}/sae.pt"
207
r2_norm_key = f
"saes/{model_slug}/l{layer}/norm.pt"
208
r2_umap_key = f
"saes/{model_slug}/l{layer}/umap.json"
209
210
print(f
"Publishing {model_slug} layer {layer}..."
, flush=
True
)
211
print(
"R2:"
, flush=
True
)
212
s3 =
_r2_client
()
213
_upload_r2
(s3, bucket, r2_sae_key, sae_abs,
"application/octet-stream"
)
214
_upload_r2
(s3, bucket, r2_norm_key, norm_path.resolve(),
"application/octet-stream"
)
215
umap_key =
_parse_flag
(args,
"--umap-key"
)
216
if
umap_path
and
umap_path.is_file():
217
_upload_r2
(s3, bucket, r2_umap_key, umap_path,
"application/json"
)
218
umap_key = umap_key
or
r2_umap_key
219
print(f
" umap: {umap_path.name}"
, flush=
True
)
220
else
:
221
print(
" umap: (skipped — run aquin sae catalog-metrics first)"
, flush=
True
)
222
223
row = {
224
"model_slug"
: model_slug,
225
"model_name"
: model_name,
226
"layer"
: layer,
227
"d_model"
: d_model,
228
"d_sae"
: d_sae,
229
"k"
: k,
230
"arch"
: arch,
231
"train_steps"
: train_steps,
232
"final_loss"
:
_parse_num
(args,
"--final-loss"
),
233
"l0"
: l0,
234
"dead_pct"
: dead_pct,
235
"interp_score"
: interp_score,
236
"umap_key"
: umap_key,
237
"r2_key"
: r2_sae_key,
238
"meta_key"
: r2_norm_key,
239
}
240
241
data =
_supabase_upsert
(row)
242
print(f
"\nRegistered: {data}"
, flush=
True
)
243
print(f
"Pull: aquin load sae {model_slug}-l{layer}"
, flush=
True
)
244
print(f
"Load: aquin load sae {model_slug}-l{{n}}"
, flush=
True
)
aquin.publish_public_sae._supabase_upsert
dict[str, Any] _supabase_upsert(dict[str, Any] row)
Definition
publish_public_sae.py:104
aquin.publish_public_sae._read_json
dict[str, Any]|None _read_json(Path path)
Definition
publish_public_sae.py:58
aquin.publish_public_sae._r2_client
_r2_client()
Definition
publish_public_sae.py:67
aquin.publish_public_sae._parse_flag
str|None _parse_flag(list[str] args, str name)
Definition
publish_public_sae.py:40
aquin.publish_public_sae._load_env_files
None _load_env_files()
Definition
publish_public_sae.py:17
aquin.publish_public_sae._parse_num
float|int|None _parse_num(list[str] args, str name, float|int|None fallback=None)
Definition
publish_public_sae.py:47
aquin.publish_public_sae._upload_r2
None _upload_r2(Any client, str bucket, str key, Path file_path, str content_type)
Definition
publish_public_sae.py:98
aquin
publish_public_sae.py
AQIT · Aquin Labs Private Limited · Apache 2.0 · Generated by
1.18.0