Enrell commited on
Commit
dcff182
·
verified ·
1 Parent(s): c553805

Upload release.sh with huggingface_hub

Browse files
Files changed (1) hide show
  1. release.sh +136 -0
release.sh ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Publish a model artifact to the Enrell/lain-parser release channel.
3
+ #
4
+ # bash release.sh <artifact-dir> <name> [--default] [--eval "note"]
5
+ # bash release.sh artifacts/e5-s2 e5-s2 --default \
6
+ # --eval "real-final-v1 int8 CPU: 137/196"
7
+ #
8
+ # Ordering is the no-break contract: model files land on the Hub first, the
9
+ # manifest pointer moves last, so a manifest always references already-verified
10
+ # content. Released names are immutable — a fix ships as a new name.
11
+ #
12
+ # --snapshot <name> pair the model with a snapshot already in the manifest
13
+ # (default: the current default model's snapshot)
14
+ # --snapshot-dir <dir> register + upload a NEW encoder snapshot (only needed
15
+ # when the model pairs with a snapshot not yet released)
16
+ # --app also push install.sh, requirements.txt and app/ —
17
+ # use whenever the vendored inference code changed
18
+ #
19
+ # Requires: hf CLI (authenticated, e.g. `hf auth login`), curl, python3.
20
+ set -euo pipefail
21
+
22
+ HF_REPO="${LAIN_PARSER_HF_REPO:-Enrell/lain-parser}"
23
+ HF_BASE="https://huggingface.co/${HF_REPO}/resolve/main"
24
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
25
+
26
+ step() { printf '\n== %s\n' "$*"; }
27
+ die() { echo "error: $*" >&2; exit 1; }
28
+ usage() { sed -n '3,18p' "$0" | sed 's/^# \{0,1\}//' >&2; exit 2; }
29
+
30
+ ART_DIR="" NAME="" DEFAULT=0 EVAL="" SNAP_DIR="" SNAP_REF="" APP=0
31
+ while [[ $# -gt 0 ]]; do
32
+ case "$1" in
33
+ --default) DEFAULT=1; shift ;;
34
+ --eval) EVAL="$2"; shift 2 ;;
35
+ --snapshot) SNAP_REF="$2"; shift 2 ;;
36
+ --snapshot-dir) SNAP_DIR="$2"; shift 2 ;;
37
+ --app) APP=1; shift ;;
38
+ -h|--help) usage ;;
39
+ -*) echo "unknown argument: $1" >&2; exit 2 ;;
40
+ *) [[ -z "$ART_DIR" ]] && ART_DIR="$1" || NAME="$1"; shift ;;
41
+ esac
42
+ done
43
+ [[ -n "$ART_DIR" && -n "$NAME" ]] || usage
44
+ [[ "$NAME" =~ ^[a-z0-9][a-z0-9.-]*$ ]] || { echo "bad model name: $NAME" >&2; exit 2; }
45
+ [[ -f "${ART_DIR}/model.int8.pt" && -f "${ART_DIR}/config.json" ]] || {
46
+ echo "error: ${ART_DIR} needs model.int8.pt + config.json" >&2; exit 1; }
47
+
48
+ command -v hf >/dev/null || die "hf CLI required (pip install huggingface_hub[cli], then hf auth login)"
49
+
50
+ step "fetching current manifest"
51
+ work="$(mktemp -d)"; trap 'rm -rf "$work"' EXIT
52
+ curl -fSL --retry 3 "${HF_BASE}/manifest.json" -o "${work}/manifest.json" \
53
+ || die "cannot read ${HF_REPO}/manifest.json"
54
+
55
+ python3 - "$work/manifest.json" "$NAME" "$SNAP_REF" <<'PY'
56
+ import json, sys
57
+ m = json.load(open(sys.argv[1]))
58
+ if sys.argv[2] in m["models"]:
59
+ sys.exit(f"error: {sys.argv[2]} already released — released names are immutable")
60
+ if sys.argv[3] and sys.argv[3] not in m["snapshots"]:
61
+ sys.exit(f"error: --snapshot {sys.argv[3]} is not in the manifest (use --snapshot-dir to register it)")
62
+ PY
63
+
64
+ SNAP_NAME="$SNAP_REF"
65
+ if [[ -n "$SNAP_DIR" ]]; then
66
+ SNAP_NAME="$(basename "$SNAP_DIR")"
67
+ [[ -d "$SNAP_DIR" ]] || die "snapshot dir not found: $SNAP_DIR"
68
+ python3 - "$work/manifest.json" "$SNAP_NAME" <<'PY'
69
+ import json, sys
70
+ m = json.load(open(sys.argv[1]))
71
+ if sys.argv[2] in m["snapshots"]:
72
+ sys.exit(f"error: snapshot {sys.argv[2]} already released — snapshots are immutable")
73
+ PY
74
+ fi
75
+
76
+ # --- upload content first --------------------------------------------------
77
+
78
+ if [[ -n "$SNAP_DIR" ]]; then
79
+ step "uploading snapshot ${SNAP_NAME}"
80
+ hf upload "$HF_REPO" "$SNAP_DIR" "snapshots/${SNAP_NAME}"
81
+ fi
82
+
83
+ step "uploading models/${NAME}"
84
+ hf upload "$HF_REPO" "$ART_DIR" "models/${NAME}"
85
+
86
+ if [[ "$APP" == 1 ]]; then
87
+ step "uploading installer + vendored app"
88
+ hf upload "$HF_REPO" "${SCRIPT_DIR}/install.sh" install.sh
89
+ hf upload "$HF_REPO" "${SCRIPT_DIR}/requirements.txt" requirements.txt
90
+ hf upload "$HF_REPO" "${SCRIPT_DIR}/app" app
91
+ fi
92
+
93
+ # --- then move the pointer -------------------------------------------------
94
+
95
+ step "updating manifest"
96
+ python3 - "$work/manifest.json" "$NAME" "$ART_DIR" "$SNAP_NAME" "$SNAP_DIR" \
97
+ "$DEFAULT" "$EVAL" "${SCRIPT_DIR}/app/PROVENANCE.json" <<'PY'
98
+ import hashlib, json, sys, pathlib, datetime
99
+ mf, name, art, snap_name, snap_dir, default, ev, prov = sys.argv[1:]
100
+ m = json.load(open(mf))
101
+ art = pathlib.Path(art)
102
+ files = {f.name: hashlib.sha256(f.read_bytes()).hexdigest()
103
+ for f in sorted(art.iterdir()) if f.is_file()}
104
+ service_ver = json.load(open(prov)).get("version", "0.0.0")
105
+ if snap_dir:
106
+ snap_dir = pathlib.Path(snap_dir)
107
+ m["snapshots"][snap_name] = {
108
+ "path": f"snapshots/{snap_name}",
109
+ "sha256": {str(f.relative_to(snap_dir)): hashlib.sha256(f.read_bytes()).hexdigest()
110
+ for f in sorted(snap_dir.rglob("*")) if f.is_file()
111
+ and "__pycache__" not in f.parts},
112
+ }
113
+ if not snap_name:
114
+ snap_name = m["models"][m["default"]]["snapshot"]
115
+ m["models"][name] = {
116
+ "path": f"models/{name}",
117
+ "snapshot": snap_name,
118
+ "files": files,
119
+ "min_service": service_ver,
120
+ "released": datetime.date.today().isoformat(),
121
+ "eval": {"note": ev} if ev else {},
122
+ }
123
+ if default == "1":
124
+ m["default"] = name
125
+ json.dump(m, open(mf, "w"), indent=2)
126
+ print(f" models[{name}] snapshot={snap_name} default={m['default']}")
127
+ PY
128
+
129
+ hf upload "$HF_REPO" "${work}/manifest.json" manifest.json \
130
+ --commit-message "release ${NAME}$([[ $DEFAULT == 1 ]] && echo ' (default)')"
131
+
132
+ step "released"
133
+ echo "name ${NAME}$([[ $DEFAULT == 1 ]] && echo ' <- new channel default')"
134
+ echo "repo https://huggingface.co/${HF_REPO}"
135
+ echo "users run bash install.sh # picks up the new default"
136
+ echo " bash install.sh --model ${NAME}"