@mcptoolshop/style-dataset-lab 3.3.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/README.es.md +117 -55
- package/README.fr.md +125 -65
- package/README.hi.md +117 -68
- package/README.it.md +119 -58
- package/README.ja.md +124 -74
- package/README.md +73 -7
- package/README.pt-BR.md +128 -63
- package/README.zh.md +116 -65
- package/bin/sdlab.js +100 -4
- package/lib/adapters/ai-toolkit.js +224 -35
- package/lib/adapters/comfyui-runner.js +79 -26
- package/lib/adapters/comfyui-workflows.js +94 -11
- package/lib/adapters/diffusers-lora.js +37 -5
- package/lib/adapters/generic-image-caption.js +43 -5
- package/lib/args.js +16 -1
- package/lib/asset-ingest.js +575 -0
- package/lib/asset-masks.js +323 -0
- package/lib/asset-source.js +1176 -0
- package/lib/batch-modes.js +29 -8
- package/lib/batch-runs.js +29 -1
- package/lib/batch-sheet-render.js +13 -3
- package/lib/brief-compiler.js +19 -0
- package/lib/candidate-sheet.js +327 -0
- package/lib/canon-author.js +350 -0
- package/lib/canon-build/build.js +146 -93
- package/lib/canon-review-gate.js +205 -0
- package/lib/captions.js +57 -0
- package/lib/card.js +74 -2
- package/lib/colab-launcher-guards.js +285 -0
- package/lib/comfyui.js +116 -18
- package/lib/config.js +116 -12
- package/lib/critique-engine.js +36 -2
- package/lib/eligibility.js +121 -9
- package/lib/eval-pack.js +14 -14
- package/lib/eval-runs.js +33 -0
- package/lib/export.js +122 -34
- package/lib/freeze-gate.js +16 -3
- package/lib/freeze-stamp.js +28 -3
- package/lib/generated-provenance.js +194 -6
- package/lib/implementation-packs.js +95 -17
- package/lib/ingest.js +396 -0
- package/lib/judgment-provenance.js +220 -0
- package/lib/lane1-render.js +668 -0
- package/lib/measure.js +422 -0
- package/lib/memory-store.js +663 -0
- package/lib/model-licences.js +305 -0
- package/lib/npy-meta.js +130 -0
- package/lib/paths.js +196 -7
- package/lib/png-meta.js +512 -0
- package/lib/records.js +54 -8
- package/lib/reingest-selected.js +113 -17
- package/lib/reingest.js +27 -3
- package/lib/run-manifest.js +159 -25
- package/lib/run-summary.js +12 -6
- package/lib/runtime-runs.js +45 -4
- package/lib/snapshot.js +114 -15
- package/lib/split.js +364 -48
- package/lib/train-runs.js +214 -0
- package/lib/training-manifests.js +114 -21
- package/lib/training-packages.js +147 -15
- package/lib/verifier.js +3 -0
- package/lib/verify-chain.js +367 -0
- package/lib/workflow-profiles.js +24 -3
- package/package.json +4 -3
- package/runtime/comfyui/txt2img-qwen.json +38 -0
- package/runtime/comfyui/txt2img-standard.json +21 -0
- package/scripts/_resume.js +62 -0
- package/scripts/_shared-args.js +44 -0
- package/scripts/asset-ingest.js +160 -0
- package/scripts/brief-compile.js +2 -1
- package/scripts/brief-show.js +2 -1
- package/scripts/canon-bind.js +2 -2
- package/scripts/canon-build.js +27 -20
- package/scripts/canon-draft.js +82 -0
- package/scripts/canon-drift.js +16 -7
- package/scripts/canon-freeze-status.js +14 -6
- package/scripts/canon-freeze.js +31 -11
- package/scripts/canon-unfreeze.js +17 -9
- package/scripts/card.js +2 -2
- package/scripts/colab/REAL_SERVICE_ASSUMPTIONS.md +14 -0
- package/scripts/colab/ai-toolkit-override-py315.txt +45 -0
- package/scripts/colab/colab_window.sh +587 -0
- package/scripts/colab/lane1_render.py +970 -0
- package/scripts/colab/launch_guards.py +564 -0
- package/scripts/colab/launch_lane1_stub.sh +309 -0
- package/scripts/colab/launch_lane3_stub.sh +366 -0
- package/scripts/colab/remote_runner.py +59 -0
- package/scripts/compare.js +2 -2
- package/scripts/critique.js +16 -1
- package/scripts/curate.js +2 -2
- package/scripts/doctor.js +182 -55
- package/scripts/eligibility.js +42 -2
- package/scripts/eval-pack.js +2 -2
- package/scripts/eval-run.js +2 -2
- package/scripts/export.js +2 -2
- package/scripts/generate-controlnet.js +45 -25
- package/scripts/generate-identity.js +53 -23
- package/scripts/generate-ipadapter.js +44 -23
- package/scripts/generate.js +86 -17
- package/scripts/implementation-pack.js +2 -2
- package/scripts/ingest.js +145 -0
- package/scripts/init.js +73 -13
- package/scripts/measure.js +170 -0
- package/scripts/measure_image.py +458 -0
- package/scripts/migrate-records.js +142 -0
- package/scripts/painterly-test.js +4 -3
- package/scripts/painterly.js +7 -3
- package/scripts/qwen_generate.py +138 -20
- package/scripts/reingest.js +2 -2
- package/scripts/sheet.js +142 -0
- package/scripts/snapshot.js +2 -2
- package/scripts/split.js +2 -2
- package/scripts/training-manifest.js +2 -2
- package/scripts/training-package.js +2 -2
- package/scripts/training-profile.js +2 -2
- package/scripts/verify.js +67 -0
- package/scripts/workflow-list.js +2 -1
- package/scripts/workflow-show.js +2 -1
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared Colab window lifecycle, lock, live wall/cap/vitals/RAM guards, token scrubber, and EXIT trap.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
colab_resolve_python() {
|
|
6
|
+
if [ -n "${SDLAB_PYTHON:-}" ] && [ -x "${SDLAB_PYTHON}" ]; then
|
|
7
|
+
echo "${SDLAB_PYTHON}"
|
|
8
|
+
return 0
|
|
9
|
+
fi
|
|
10
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
11
|
+
command -v python3
|
|
12
|
+
return 0
|
|
13
|
+
fi
|
|
14
|
+
command -v python
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
colab_refuse_nonempty_out_dir() {
|
|
18
|
+
local out_dir="$1"
|
|
19
|
+
if [ -d "${out_dir}" ] && [ -n "$(ls -A "${out_dir}" 2>/dev/null)" ]; then
|
|
20
|
+
echo "REFUSED: OUT_DIR_NOT_EMPTY: ${out_dir} already exists and is not empty" >&2
|
|
21
|
+
return 97
|
|
22
|
+
fi
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
colab_guard_fake_cli_if_test() {
|
|
26
|
+
local colab_bin
|
|
27
|
+
colab_bin="$(command -v colab || true)"
|
|
28
|
+
if [ -z "${colab_bin}" ]; then
|
|
29
|
+
echo "REFUSED: colab CLI not found in PATH" >&2
|
|
30
|
+
return 98
|
|
31
|
+
fi
|
|
32
|
+
if [ "${SDLAB_TEST_FAKE_COLAB:-0}" = "1" ]; then
|
|
33
|
+
if ! grep -q "^#SDLAB_FAKE_COLAB_CLI=1$" "${colab_bin}" 2>/dev/null; then
|
|
34
|
+
echo "REFUSED: REAL_COLAB_FORBIDDEN: colab resolved to ${colab_bin} without #SDLAB_FAKE_COLAB_CLI=1 marker" >&2
|
|
35
|
+
return 99
|
|
36
|
+
fi
|
|
37
|
+
fi
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
colab_verify_input_shas() {
|
|
41
|
+
local py_bin="$1"
|
|
42
|
+
local guards_py="$2"
|
|
43
|
+
shift 2
|
|
44
|
+
local args=()
|
|
45
|
+
for pair in "$@"; do
|
|
46
|
+
args+=("--verify-sha" "${pair}")
|
|
47
|
+
done
|
|
48
|
+
"${py_bin}" "${guards_py}" "${args[@]}" </dev/null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
colab_poll_usage_and_ram() {
|
|
52
|
+
local py_bin="$1"
|
|
53
|
+
local out_dir="$2"
|
|
54
|
+
local fault_mode="${SDLAB_FAULT_POLL_HELPER:-}"
|
|
55
|
+
if [ -f "${out_dir}/start_balance.txt" ] && [ -n "${SDLAB_FAULT_POLL_HELPER_MIDRUN:-}" ]; then
|
|
56
|
+
fault_mode="${SDLAB_FAULT_POLL_HELPER_MIDRUN}"
|
|
57
|
+
fi
|
|
58
|
+
if [ "${fault_mode}" = "crash" ]; then
|
|
59
|
+
return 137
|
|
60
|
+
elif [ "${fault_mode}" = "empty" ]; then
|
|
61
|
+
return 0
|
|
62
|
+
elif [ "${fault_mode}" = "short" ]; then
|
|
63
|
+
echo "9.85 9.85"
|
|
64
|
+
return 0
|
|
65
|
+
fi
|
|
66
|
+
local default_rate="${RATE_CCU_PER_HOUR:-0.26}"
|
|
67
|
+
local guards_py
|
|
68
|
+
guards_py="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/launch_guards.py"
|
|
69
|
+
|
|
70
|
+
local usage_text=""
|
|
71
|
+
local usage_rc=0
|
|
72
|
+
usage_text="$(colab usage </dev/null 2>>"${out_dir}/colab.log")" || usage_rc=$?
|
|
73
|
+
printf '%s\n' "${usage_text}" >> "${out_dir}/usage.log"
|
|
74
|
+
|
|
75
|
+
"${py_bin}" - "${out_dir}" "${default_rate}" "${usage_rc}" "${usage_text}" "${guards_py}" << 'PYEOF'
|
|
76
|
+
import os
|
|
77
|
+
import re
|
|
78
|
+
import sys
|
|
79
|
+
from pathlib import Path
|
|
80
|
+
|
|
81
|
+
out_dir = Path(sys.argv[1])
|
|
82
|
+
default_rate = float(sys.argv[2])
|
|
83
|
+
usage_rc = int(sys.argv[3])
|
|
84
|
+
raw_usage = sys.argv[4]
|
|
85
|
+
guards_py = Path(sys.argv[5])
|
|
86
|
+
|
|
87
|
+
sys.path.insert(0, str(guards_py.parent))
|
|
88
|
+
import launch_guards as lg
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _read_float(p: Path) -> float | None:
|
|
92
|
+
if not p.exists():
|
|
93
|
+
return None
|
|
94
|
+
try:
|
|
95
|
+
return float(p.read_text(encoding="utf-8").strip())
|
|
96
|
+
except Exception:
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
start_f = out_dir / "start_balance.txt"
|
|
101
|
+
end_f = out_dir / "end_balance.txt"
|
|
102
|
+
rate_f = out_dir / "rate_measured.txt"
|
|
103
|
+
rate_pre_f = out_dir / "rate_measured_pre_open.txt"
|
|
104
|
+
rate_post_f = out_dir / "rate_measured_post_open.txt"
|
|
105
|
+
rate_proj_f = out_dir / "rate_for_projection.txt"
|
|
106
|
+
rate_src_f = out_dir / "rate_source.txt"
|
|
107
|
+
err_f = out_dir / "usage_parse_error.txt"
|
|
108
|
+
opened_marker = out_dir / "session_opened.txt"
|
|
109
|
+
|
|
110
|
+
session_opened = opened_marker.exists()
|
|
111
|
+
existing_pre = _read_float(rate_pre_f)
|
|
112
|
+
existing_post = _read_float(rate_post_f)
|
|
113
|
+
|
|
114
|
+
parsed = lg.parse_usage_text(
|
|
115
|
+
raw_usage,
|
|
116
|
+
usage_rc=usage_rc,
|
|
117
|
+
configured_rate=default_rate,
|
|
118
|
+
session_opened=session_opened,
|
|
119
|
+
post_open_rate=existing_post,
|
|
120
|
+
pre_open_rate=existing_pre,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if parsed["ok"]:
|
|
124
|
+
bal = parsed["balance"]
|
|
125
|
+
if not start_f.exists():
|
|
126
|
+
start_f.write_text(f"{bal}\n", encoding="utf-8", newline="\n")
|
|
127
|
+
start_bal_str = start_f.read_text(encoding="utf-8").strip()
|
|
128
|
+
end_f.write_text(f"{bal}\n", encoding="utf-8", newline="\n")
|
|
129
|
+
cur_bal_str = f"{bal}"
|
|
130
|
+
if parsed["rate_measured_pre_open"] is not None:
|
|
131
|
+
rate_pre_f.write_text(f"{parsed['rate_measured_pre_open']}\n", encoding="utf-8", newline="\n")
|
|
132
|
+
if parsed["rate_measured_post_open"] is not None:
|
|
133
|
+
rate_post_f.write_text(f"{parsed['rate_measured_post_open']}\n", encoding="utf-8", newline="\n")
|
|
134
|
+
if parsed["rate_measured_post_open"] is not None:
|
|
135
|
+
rate_f.write_text(f"{parsed['rate_measured_post_open']}\n", encoding="utf-8", newline="\n")
|
|
136
|
+
elif parsed["rate_measured_pre_open"] is not None:
|
|
137
|
+
rate_f.write_text(f"{parsed['rate_measured_pre_open']}\n", encoding="utf-8", newline="\n")
|
|
138
|
+
rate_proj_f.write_text(f"{parsed['rate_for_projection']}\n", encoding="utf-8", newline="\n")
|
|
139
|
+
rate_src_f.write_text(f"{parsed['rate_source']}\n", encoding="utf-8", newline="\n")
|
|
140
|
+
err_tok = "NONE"
|
|
141
|
+
else:
|
|
142
|
+
err_msg = str(parsed["error"] or "COLAB_USAGE_PARSE_FAILED")
|
|
143
|
+
err_f.write_text(f"{err_msg}\n", encoding="utf-8", newline="\n")
|
|
144
|
+
if end_f.exists():
|
|
145
|
+
end_f.unlink()
|
|
146
|
+
start_bal_str = start_f.read_text(encoding="utf-8").strip() if start_f.exists() else "NONE"
|
|
147
|
+
cur_bal_str = "NONE"
|
|
148
|
+
rate_proj_f.write_text(f"{parsed['rate_for_projection']}\n", encoding="utf-8", newline="\n")
|
|
149
|
+
rate_src_f.write_text(f"{parsed['rate_source']}\n", encoding="utf-8", newline="\n")
|
|
150
|
+
err_tok = "COLAB_USAGE_PARSE_FAILED"
|
|
151
|
+
|
|
152
|
+
# Parse VM /proc/meminfo and cgroup memory from vitals.log when present
|
|
153
|
+
anon_mb = float(os.environ.get("CURRENT_ANON_MB", "512")) if "os" in sys.modules else 512.0
|
|
154
|
+
mem_total_mb = 12288.0
|
|
155
|
+
cgroup_max_mb = ""
|
|
156
|
+
vitals_f = out_dir / "vitals.log"
|
|
157
|
+
if vitals_f.exists():
|
|
158
|
+
vtxt = vitals_f.read_text(encoding="utf-8", errors="replace")
|
|
159
|
+
m_tot = re.findall(r"MemTotal:\s*(\d+)\s*kB", vtxt)
|
|
160
|
+
if m_tot:
|
|
161
|
+
mem_total_mb = round(int(m_tot[-1]) / 1024.0, 1)
|
|
162
|
+
m_anon = re.findall(r"AnonPages:\s*(\d+)\s*kB", vtxt)
|
|
163
|
+
if m_anon:
|
|
164
|
+
anon_mb = round(int(m_anon[-1]) / 1024.0, 1)
|
|
165
|
+
m_cg_cur = re.findall(r"CGROUP_MEM_CURRENT:\s*(\d+)", vtxt)
|
|
166
|
+
if m_cg_cur:
|
|
167
|
+
anon_mb = round(int(m_cg_cur[-1]) / (1024.0 * 1024.0), 1)
|
|
168
|
+
m_cg_max = re.findall(r"CGROUP_MEM_MAX:\s*(\d+)", vtxt)
|
|
169
|
+
if m_cg_max:
|
|
170
|
+
cgroup_max_mb = str(round(int(m_cg_max[-1]) / (1024.0 * 1024.0), 1))
|
|
171
|
+
|
|
172
|
+
print(f"{start_bal_str} {cur_bal_str} {parsed['rate_for_projection']} {err_tok} {anon_mb} {mem_total_mb} {cgroup_max_mb}")
|
|
173
|
+
PYEOF
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
colab_check_cap_and_ram() {
|
|
177
|
+
local py_bin="$1"
|
|
178
|
+
local guards_py="$2"
|
|
179
|
+
local mode="$3"
|
|
180
|
+
local elapsed_s="$4"
|
|
181
|
+
local wall_s="$5"
|
|
182
|
+
local grace_s="$6"
|
|
183
|
+
local cap="$7"
|
|
184
|
+
local margin="$8"
|
|
185
|
+
local out_dir="$9"
|
|
186
|
+
|
|
187
|
+
local metrics=""
|
|
188
|
+
local poll_rc=0
|
|
189
|
+
metrics="$(colab_poll_usage_and_ram "${py_bin}" "${out_dir}")" || poll_rc=$?
|
|
190
|
+
local start_bal="" cur_bal="" rate_val="" err_tok="" anon_mb="" mem_total_mb="" cgroup_max_mb=""
|
|
191
|
+
if [ "${poll_rc}" -ne 0 ]; then
|
|
192
|
+
err_tok="COLAB_USAGE_PARSE_FAILED"
|
|
193
|
+
printf 'COLAB_USAGE_PARSE_FAILED: usage poll helper exited with status %s\n' "${poll_rc}" > "${out_dir}/usage_parse_error.txt"
|
|
194
|
+
else
|
|
195
|
+
read -r start_bal cur_bal rate_val err_tok anon_mb mem_total_mb cgroup_max_mb <<< "${metrics}"
|
|
196
|
+
if [ -z "${metrics}" ] || [ -z "${start_bal}" ] || [ -z "${cur_bal}" ] || [ -z "${rate_val}" ] || [ -z "${err_tok}" ] || [ "${start_bal}" = "NONE" ] || [ "${cur_bal}" = "NONE" ]; then
|
|
197
|
+
err_tok="COLAB_USAGE_PARSE_FAILED"
|
|
198
|
+
if [ ! -s "${out_dir}/usage_parse_error.txt" ]; then
|
|
199
|
+
printf 'COLAB_USAGE_PARSE_FAILED: incomplete or missing usage metrics (%s)\n' "${metrics}" > "${out_dir}/usage_parse_error.txt"
|
|
200
|
+
fi
|
|
201
|
+
fi
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
local guard_args=(
|
|
205
|
+
"--mode" "${mode}"
|
|
206
|
+
"--elapsed-s" "${elapsed_s}"
|
|
207
|
+
"--wall-s" "${wall_s}"
|
|
208
|
+
"--grace-s" "${grace_s}"
|
|
209
|
+
"--cap" "${cap}"
|
|
210
|
+
"--margin" "${margin}"
|
|
211
|
+
"--rate" "${rate_val:-0.26}"
|
|
212
|
+
)
|
|
213
|
+
if [ "${start_bal}" != "NONE" ] && [ -n "${start_bal}" ]; then
|
|
214
|
+
guard_args+=("--start-balance" "${start_bal}")
|
|
215
|
+
fi
|
|
216
|
+
if [ "${cur_bal}" != "NONE" ] && [ -n "${cur_bal}" ]; then
|
|
217
|
+
guard_args+=("--current-balance" "${cur_bal}")
|
|
218
|
+
fi
|
|
219
|
+
if [ "${err_tok}" != "NONE" ] && [ -n "${err_tok}" ]; then
|
|
220
|
+
local err_detail="${err_tok}"
|
|
221
|
+
if [ -f "${out_dir}/usage_parse_error.txt" ]; then
|
|
222
|
+
err_detail="$(cat "${out_dir}/usage_parse_error.txt")"
|
|
223
|
+
fi
|
|
224
|
+
guard_args+=("--usage-parse-error" "${err_detail}")
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
local guard_rc=0
|
|
228
|
+
"${py_bin}" "${guards_py}" "${guard_args[@]}" </dev/null >> "${out_dir}/guards.log" || guard_rc=$?
|
|
229
|
+
if [ "${guard_rc}" -ne 0 ]; then
|
|
230
|
+
if [ "${err_tok}" != "NONE" ] && [ -n "${err_tok}" ]; then
|
|
231
|
+
echo "REFUSED: COLAB_USAGE_PARSE_FAILED" >&2
|
|
232
|
+
fi
|
|
233
|
+
return 1
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
local ram_args=(
|
|
237
|
+
"--check-ram"
|
|
238
|
+
"--current-anon-mb" "${CURRENT_ANON_MB:-${anon_mb:-512}}"
|
|
239
|
+
"--projected-delta-mb" "${PROJECTED_DELTA_MB:-2048}"
|
|
240
|
+
"--mem-total-mb" "${MEM_TOTAL_MB:-${mem_total_mb:-12288}}"
|
|
241
|
+
)
|
|
242
|
+
if [ -n "${cgroup_max_mb:-}" ]; then
|
|
243
|
+
ram_args+=("--cgroup-max-mb" "${cgroup_max_mb}")
|
|
244
|
+
fi
|
|
245
|
+
local ram_rc=0
|
|
246
|
+
"${py_bin}" "${guards_py}" "${ram_args[@]}" </dev/null >> "${out_dir}/guards.log" || ram_rc=$?
|
|
247
|
+
if [ "${ram_rc}" -ne 0 ]; then
|
|
248
|
+
return "${ram_rc}"
|
|
249
|
+
fi
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
colab_run_exec_with_live_guards() {
|
|
253
|
+
local py_bin="$1"
|
|
254
|
+
local guards_py="$2"
|
|
255
|
+
local session="$3"
|
|
256
|
+
local runner_py="$4"
|
|
257
|
+
local start_epoch="$5"
|
|
258
|
+
local wall_s="$6"
|
|
259
|
+
local grace_s="$7"
|
|
260
|
+
local cap="$8"
|
|
261
|
+
local margin="$9"
|
|
262
|
+
local out_dir="${10}"
|
|
263
|
+
shift 10
|
|
264
|
+
local env_pairs=("$@")
|
|
265
|
+
|
|
266
|
+
local poll_s="${GUARD_POLL_INTERVAL_S:-2}"
|
|
267
|
+
local stall_s="${STALL_S:-300}"
|
|
268
|
+
local setup_stall_s="${SETUP_STALL_S:-600}"
|
|
269
|
+
local last_size=0
|
|
270
|
+
local last_progress_epoch
|
|
271
|
+
last_progress_epoch="$(date -u +%s)"
|
|
272
|
+
local last_setup_bytes=0
|
|
273
|
+
local last_setup_progress_epoch="${last_progress_epoch}"
|
|
274
|
+
|
|
275
|
+
local wall_int="${wall_s%.*}"
|
|
276
|
+
local grace_int="${grace_s%.*}"
|
|
277
|
+
local exec_timeout="$(( wall_int + grace_int + 60 ))"
|
|
278
|
+
|
|
279
|
+
local exec_args=("-s" "${session}" "-f" "${runner_py}" "--timeout" "${exec_timeout}")
|
|
280
|
+
for kv in "${env_pairs[@]}"; do
|
|
281
|
+
exec_args+=("--env" "${kv}")
|
|
282
|
+
done
|
|
283
|
+
|
|
284
|
+
colab exec "${exec_args[@]}" </dev/null > "${out_dir}/vitals.log" 2>&1 &
|
|
285
|
+
EXEC_PID="$!"
|
|
286
|
+
|
|
287
|
+
while kill -0 "${EXEC_PID}" 2>/dev/null; do
|
|
288
|
+
sleep "${poll_s}"
|
|
289
|
+
if ! kill -0 "${EXEC_PID}" 2>/dev/null; then
|
|
290
|
+
break
|
|
291
|
+
fi
|
|
292
|
+
local now_epoch elapsed_s cur_size idle_s cur_setup_bytes setup_idle_s run_only_bytes
|
|
293
|
+
now_epoch="$(date -u +%s)"
|
|
294
|
+
elapsed_s="$((now_epoch - start_epoch))"
|
|
295
|
+
|
|
296
|
+
local check_rc=0
|
|
297
|
+
colab_check_cap_and_ram "${py_bin}" "${guards_py}" "live" "${elapsed_s}" "${wall_s}" "${grace_s}" "${cap}" "${margin}" "${out_dir}" || check_rc=$?
|
|
298
|
+
if [ "${check_rc}" -ne 0 ]; then
|
|
299
|
+
pkill -P "${EXEC_PID}" 2>/dev/null || true
|
|
300
|
+
kill -9 "${EXEC_PID}" 2>/dev/null || true
|
|
301
|
+
wait "${EXEC_PID}" 2>/dev/null || true
|
|
302
|
+
EXEC_PID=""
|
|
303
|
+
return 91
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
run_only_bytes="$(grep -Ev '^(===[[:space:]]*VITALS|CGROUP_MEM(_CURRENT|_MAX)?:|MEM:|SETUP_BYTES:|PROC:|HARNESS:|[[:space:]]*[0-9]+[[:space:]]+.*(comfy|main\.py|ai-toolkit|run\.py|llama-server|ollama|lane1_render\.py))' "${out_dir}/vitals.log" 2>/dev/null | wc -c | tr -d '[:space:]' || echo 0)"
|
|
307
|
+
cur_size="${run_only_bytes:-0}"
|
|
308
|
+
if [ "${cur_size:-0}" != "${last_size}" ]; then
|
|
309
|
+
last_size="${cur_size:-0}"
|
|
310
|
+
last_progress_epoch="${now_epoch}"
|
|
311
|
+
fi
|
|
312
|
+
idle_s="$((now_epoch - last_progress_epoch))"
|
|
313
|
+
|
|
314
|
+
cur_setup_bytes="$(grep -E '^SETUP_BYTES:[[:space:]]*[0-9]+[[:space:]]*$' "${out_dir}/vitals.log" 2>/dev/null | tail -n 1 | awk -F: '{gsub(/[[:space:]]/, "", $2); print $2}' || true)"
|
|
315
|
+
if [ -n "${cur_setup_bytes}" ] && [ "${cur_setup_bytes}" != "${last_setup_bytes}" ]; then
|
|
316
|
+
last_setup_bytes="${cur_setup_bytes}"
|
|
317
|
+
last_setup_progress_epoch="${now_epoch}"
|
|
318
|
+
fi
|
|
319
|
+
setup_idle_s="$((now_epoch - last_setup_progress_epoch))"
|
|
320
|
+
|
|
321
|
+
local vitals_rc=0
|
|
322
|
+
"${py_bin}" "${guards_py}" \
|
|
323
|
+
--vitals-file "${out_dir}/vitals.log" \
|
|
324
|
+
--idle-seconds "${idle_s}" \
|
|
325
|
+
--setup-idle-seconds "${setup_idle_s}" \
|
|
326
|
+
--stall-s "${stall_s}" \
|
|
327
|
+
--setup-stall-s "${setup_stall_s}" </dev/null >> "${out_dir}/guards.log" || vitals_rc=$?
|
|
328
|
+
if [ "${vitals_rc}" -ne 0 ]; then
|
|
329
|
+
pkill -P "${EXEC_PID}" 2>/dev/null || true
|
|
330
|
+
kill -9 "${EXEC_PID}" 2>/dev/null || true
|
|
331
|
+
wait "${EXEC_PID}" 2>/dev/null || true
|
|
332
|
+
EXEC_PID=""
|
|
333
|
+
return 93
|
|
334
|
+
fi
|
|
335
|
+
done
|
|
336
|
+
|
|
337
|
+
local exec_rc=0
|
|
338
|
+
wait "${EXEC_PID}" || exec_rc=$?
|
|
339
|
+
EXEC_PID=""
|
|
340
|
+
|
|
341
|
+
colab_scrub_all_logs "${py_bin}" "${guards_py}" "${out_dir}"
|
|
342
|
+
|
|
343
|
+
local marker_line=""
|
|
344
|
+
marker_line="$(grep -E '^SDLAB_REMOTE_RC=-?[0-9]+$' "${out_dir}/vitals.log" 2>/dev/null | tail -n 1 | tr -d '\r' || true)"
|
|
345
|
+
if [ -z "${marker_line}" ]; then
|
|
346
|
+
echo "REFUSED: SDLAB_REMOTE_RC marker missing from vitals.log (colab exec_rc=${exec_rc})" >> "${out_dir}/guards.log"
|
|
347
|
+
if [ "${exec_rc}" -ne 0 ]; then
|
|
348
|
+
return "${exec_rc}"
|
|
349
|
+
fi
|
|
350
|
+
return 94
|
|
351
|
+
fi
|
|
352
|
+
local remote_rc="${marker_line#SDLAB_REMOTE_RC=}"
|
|
353
|
+
if [ "${remote_rc}" -ne 0 ]; then
|
|
354
|
+
echo "REFUSED: remote runner reported SDLAB_REMOTE_RC=${remote_rc}" >> "${out_dir}/guards.log"
|
|
355
|
+
return "${remote_rc}"
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
"${py_bin}" "${guards_py}" --vitals-file "${out_dir}/vitals.log" --idle-seconds 0 --setup-idle-seconds 0 --stall-s "${stall_s}" --setup-stall-s "${setup_stall_s}" </dev/null >> "${out_dir}/guards.log"
|
|
359
|
+
local end_epoch elapsed_final
|
|
360
|
+
end_epoch="$(date -u +%s)"
|
|
361
|
+
elapsed_final="$((end_epoch - start_epoch))"
|
|
362
|
+
colab_check_cap_and_ram "${py_bin}" "${guards_py}" "live" "${elapsed_final}" "${wall_s}" "${grace_s}" "${cap}" "${margin}" "${out_dir}"
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
colab_stop_and_log_closing() {
|
|
366
|
+
local py_bin="$1"
|
|
367
|
+
local session="$2"
|
|
368
|
+
local out_dir="$3"
|
|
369
|
+
colab stop -s "${session}" </dev/null >> "${out_dir}/colab.log" 2>&1 || true
|
|
370
|
+
colab sessions </dev/null >> "${out_dir}/colab.log" 2>&1 || true
|
|
371
|
+
colab_poll_usage_and_ram "${py_bin}" "${out_dir}" >/dev/null 2>&1 || true
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
colab_open_session_locked() {
|
|
375
|
+
local session="$1"
|
|
376
|
+
local card="$2"
|
|
377
|
+
local out_dir="$3"
|
|
378
|
+
local lock_file="${COLAB_OPEN_LOCK:-/tmp/colab-window-open.lock}"
|
|
379
|
+
mkdir -p "$(dirname "${lock_file}")"
|
|
380
|
+
|
|
381
|
+
local new_args=()
|
|
382
|
+
if [ "${card}" = "CPU-high-mem" ] || [ "${card}" = "CPU" ]; then
|
|
383
|
+
new_args=("new" "--high-mem" "-s" "${session}")
|
|
384
|
+
else
|
|
385
|
+
new_args=("new" "--gpu" "${card}" "--high-mem" "-s" "${session}")
|
|
386
|
+
fi
|
|
387
|
+
|
|
388
|
+
if command -v flock >/dev/null 2>&1; then
|
|
389
|
+
(
|
|
390
|
+
flock -x 9
|
|
391
|
+
printf '1\n' > "${lock_file}.held"
|
|
392
|
+
colab "${new_args[@]}" </dev/null >> "${out_dir}/colab.log" 2>&1
|
|
393
|
+
rm -f "${lock_file}.held"
|
|
394
|
+
) 9>"${lock_file}"
|
|
395
|
+
else
|
|
396
|
+
local lock_dir="${lock_file}.lockdir"
|
|
397
|
+
while ! mkdir "${lock_dir}" 2>/dev/null; do
|
|
398
|
+
sleep 0.1
|
|
399
|
+
done
|
|
400
|
+
printf '1\n' > "${lock_file}.held"
|
|
401
|
+
local rc=0
|
|
402
|
+
colab "${new_args[@]}" </dev/null >> "${out_dir}/colab.log" 2>&1 || rc=$?
|
|
403
|
+
rm -f "${lock_file}.held"
|
|
404
|
+
rmdir "${lock_dir}" 2>/dev/null || true
|
|
405
|
+
if [ "${rc}" -ne 0 ]; then
|
|
406
|
+
return "${rc}"
|
|
407
|
+
fi
|
|
408
|
+
fi
|
|
409
|
+
printf '1\n' > "${out_dir}/session_opened.txt"
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
colab_scrub_all_logs() {
|
|
413
|
+
local py_bin="$1"
|
|
414
|
+
local guards_py="$2"
|
|
415
|
+
local out_dir="$3"
|
|
416
|
+
if [ ! -d "${out_dir}" ]; then
|
|
417
|
+
return 0
|
|
418
|
+
fi
|
|
419
|
+
local scrub_args=()
|
|
420
|
+
while IFS= read -r -d '' f; do
|
|
421
|
+
scrub_args+=("--scrub-file" "${f}")
|
|
422
|
+
done < <(find "${out_dir}" -type f \( -name "*.log" -o -name "*.json" -o -name "*.txt" -o -name "*.sh" \) -print0 2>/dev/null || true)
|
|
423
|
+
if [ "${#scrub_args[@]}" -gt 0 ]; then
|
|
424
|
+
"${py_bin}" "${guards_py}" "${scrub_args[@]}" </dev/null || true
|
|
425
|
+
fi
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
colab_write_window_and_outcome() {
|
|
429
|
+
local py_bin="$1"
|
|
430
|
+
local out_dir="$2"
|
|
431
|
+
local lane="$3"
|
|
432
|
+
local session="$4"
|
|
433
|
+
local card="$5"
|
|
434
|
+
local cap="$6"
|
|
435
|
+
local wall_s="$7"
|
|
436
|
+
local grace_s="$8"
|
|
437
|
+
local margin="$9"
|
|
438
|
+
local opened_at="${10}"
|
|
439
|
+
local closed_at="${11}"
|
|
440
|
+
local exit_code="${12}"
|
|
441
|
+
local shas_json="${13}"
|
|
442
|
+
|
|
443
|
+
"${py_bin}" - "${out_dir}" "${lane}" "${session}" "${card}" "${cap}" "${wall_s}" "${grace_s}" "${margin}" "${opened_at}" "${closed_at}" "${exit_code}" "${shas_json}" << 'PYEOF'
|
|
444
|
+
import hashlib
|
|
445
|
+
import json
|
|
446
|
+
import sys
|
|
447
|
+
import tarfile
|
|
448
|
+
from pathlib import Path
|
|
449
|
+
|
|
450
|
+
out_dir = Path(sys.argv[1])
|
|
451
|
+
lane = sys.argv[2]
|
|
452
|
+
session = sys.argv[3]
|
|
453
|
+
card = sys.argv[4]
|
|
454
|
+
cap = float(sys.argv[5])
|
|
455
|
+
wall_s = int(float(sys.argv[6]))
|
|
456
|
+
grace_s = int(float(sys.argv[7]))
|
|
457
|
+
margin = float(sys.argv[8])
|
|
458
|
+
opened_at = sys.argv[9]
|
|
459
|
+
closed_at = sys.argv[10]
|
|
460
|
+
exit_code = int(sys.argv[11])
|
|
461
|
+
shas_raw = json.loads(sys.argv[12])
|
|
462
|
+
|
|
463
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def sha256_path(p: Path) -> str:
|
|
467
|
+
h = hashlib.sha256()
|
|
468
|
+
with p.open("rb") as f:
|
|
469
|
+
for chunk in iter(lambda: f.read(1 << 20), b""):
|
|
470
|
+
h.update(chunk)
|
|
471
|
+
return "sha256:" + h.hexdigest()
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def read_float_file(p: Path) -> float | None:
|
|
475
|
+
if not p.exists():
|
|
476
|
+
return None
|
|
477
|
+
try:
|
|
478
|
+
return float(p.read_text(encoding="utf-8").strip())
|
|
479
|
+
except Exception:
|
|
480
|
+
return None
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def read_text_file(p: Path) -> str | None:
|
|
484
|
+
if not p.exists():
|
|
485
|
+
return None
|
|
486
|
+
val = p.read_text(encoding="utf-8", errors="replace").strip()
|
|
487
|
+
return val if val else None
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
vm_facts: dict = {}
|
|
491
|
+
vm_win_file = out_dir / "vm_window.json"
|
|
492
|
+
if vm_win_file.exists():
|
|
493
|
+
try:
|
|
494
|
+
vm_facts.update(json.loads(vm_win_file.read_text(encoding="utf-8")))
|
|
495
|
+
except Exception:
|
|
496
|
+
pass
|
|
497
|
+
|
|
498
|
+
for tar_p in sorted(out_dir.glob("*.tar.gz")):
|
|
499
|
+
if tarfile.is_tarfile(tar_p):
|
|
500
|
+
try:
|
|
501
|
+
with tarfile.open(tar_p, "r:gz") as tf:
|
|
502
|
+
for member in tf.getmembers():
|
|
503
|
+
if Path(member.name).name == "vm_window.json":
|
|
504
|
+
ext = tf.extractfile(member)
|
|
505
|
+
if ext is not None:
|
|
506
|
+
vm_facts.update(json.loads(ext.read().decode("utf-8")))
|
|
507
|
+
except Exception:
|
|
508
|
+
pass
|
|
509
|
+
|
|
510
|
+
vitals_p = out_dir / "vitals.log"
|
|
511
|
+
if vitals_p.exists():
|
|
512
|
+
for line in vitals_p.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
513
|
+
if line.startswith("WINDOW_JSON "):
|
|
514
|
+
try:
|
|
515
|
+
vm_facts.update(json.loads(line[len("WINDOW_JSON ") :].strip()))
|
|
516
|
+
except Exception:
|
|
517
|
+
pass
|
|
518
|
+
|
|
519
|
+
downloaded_files: dict[str, str] = {}
|
|
520
|
+
for artifact in sorted(list(out_dir.glob("*.tar.gz")) + list(out_dir.glob("*.safetensors"))):
|
|
521
|
+
if artifact.is_file():
|
|
522
|
+
downloaded_files[artifact.name] = sha256_path(artifact)
|
|
523
|
+
|
|
524
|
+
usage_parse_error = read_text_file(out_dir / "usage_parse_error.txt")
|
|
525
|
+
rate_measured_pre_open = read_float_file(out_dir / "rate_measured_pre_open.txt")
|
|
526
|
+
rate_measured_post_open = read_float_file(out_dir / "rate_measured_post_open.txt")
|
|
527
|
+
rate_measured = (
|
|
528
|
+
rate_measured_post_open
|
|
529
|
+
if rate_measured_post_open is not None
|
|
530
|
+
else read_float_file(out_dir / "rate_measured.txt")
|
|
531
|
+
)
|
|
532
|
+
rate_for_projection = read_float_file(out_dir / "rate_for_projection.txt")
|
|
533
|
+
rate_used_for_cap_and_projection = rate_for_projection
|
|
534
|
+
rate_source = read_text_file(out_dir / "rate_source.txt") or (
|
|
535
|
+
"post_open_measured"
|
|
536
|
+
if (rate_measured_post_open is not None and rate_measured_post_open > 0.0)
|
|
537
|
+
else (
|
|
538
|
+
"configured_pending_post_open_rate"
|
|
539
|
+
if (rate_measured_post_open is not None and rate_measured_post_open == 0.0)
|
|
540
|
+
else (
|
|
541
|
+
"max_of_pre_open_and_configured"
|
|
542
|
+
if rate_measured_pre_open is not None
|
|
543
|
+
else "configured"
|
|
544
|
+
)
|
|
545
|
+
)
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
window_payload = {
|
|
549
|
+
**vm_facts,
|
|
550
|
+
"window_id": session,
|
|
551
|
+
"lane": lane,
|
|
552
|
+
"session": session,
|
|
553
|
+
"card": card,
|
|
554
|
+
"cap": cap,
|
|
555
|
+
"wall_s": wall_s,
|
|
556
|
+
"grace_s": grace_s,
|
|
557
|
+
"margin": margin,
|
|
558
|
+
"start_balance": read_float_file(out_dir / "start_balance.txt"),
|
|
559
|
+
"end_balance": read_float_file(out_dir / "end_balance.txt"),
|
|
560
|
+
"rate_measured": rate_measured,
|
|
561
|
+
"rate_measured_pre_open": rate_measured_pre_open,
|
|
562
|
+
"rate_measured_post_open": rate_measured_post_open,
|
|
563
|
+
"rate_used_for_cap_and_projection": rate_used_for_cap_and_projection,
|
|
564
|
+
"rate_for_projection": rate_for_projection,
|
|
565
|
+
"rate_source": rate_source,
|
|
566
|
+
"usage_parse_error": usage_parse_error,
|
|
567
|
+
"input_shas": shas_raw.get("verified_shas", shas_raw),
|
|
568
|
+
"downloaded_files": downloaded_files,
|
|
569
|
+
"opened_at": opened_at,
|
|
570
|
+
"closed_at": closed_at,
|
|
571
|
+
"exit_code": exit_code,
|
|
572
|
+
}
|
|
573
|
+
(out_dir / "window.json").write_text(json.dumps(window_payload, indent=2) + "\n", encoding="utf-8", newline="\n")
|
|
574
|
+
|
|
575
|
+
outcome_payload = {
|
|
576
|
+
"window_id": session,
|
|
577
|
+
"lane": lane,
|
|
578
|
+
"session": session,
|
|
579
|
+
"card": card,
|
|
580
|
+
"status": "ok" if exit_code == 0 else "failed",
|
|
581
|
+
"exit_code": exit_code,
|
|
582
|
+
"opened_at": opened_at,
|
|
583
|
+
"closed_at": closed_at,
|
|
584
|
+
}
|
|
585
|
+
(out_dir / "outcome.draft.json").write_text(json.dumps(outcome_payload, indent=2) + "\n", encoding="utf-8", newline="\n")
|
|
586
|
+
PYEOF
|
|
587
|
+
}
|