Maslionok commited on
Commit
014f622
·
1 Parent(s): 88774ef

added posibility to select older multilingual spaces

Browse files
Files changed (2) hide show
  1. README.md +6 -2
  2. app.py +153 -21
README.md CHANGED
@@ -22,11 +22,15 @@ The app does not use `aligned_all.vec`.
22
 
23
  ## Runtime configuration
24
 
25
- By default, the app scans the stage 6 prefix and downloads the newest artifact
26
- folder that contains `config.json`:
27
 
28
  `s3://131-component-staging/multilingual-static-word-embeddings/stage-6/`
29
 
 
 
 
 
30
  Set these Hugging Face Space secrets for S3-compatible storage:
31
 
32
  - `SE_ACCESS_KEY`
 
22
 
23
  ## Runtime configuration
24
 
25
+ By default, the app scans the stage 6 prefix, downloads the newest artifact
26
+ folder that contains `config.json`, and selects it in the UI:
27
 
28
  `s3://131-component-staging/multilingual-static-word-embeddings/stage-6/`
29
 
30
+ Older artifact folders found under the same prefix are available from the
31
+ Dictionary artifact dropdown. Switching artifacts reloads the aligned space and
32
+ updates the displayed load time.
33
+
34
  Set these Hugging Face Space secrets for S3-compatible storage:
35
 
36
  - `SE_ACCESS_KEY`
app.py CHANGED
@@ -5,6 +5,7 @@ import json
5
  import os
6
  import re
7
  import sys
 
8
  import unicodedata
9
  from dataclasses import dataclass
10
  from functools import lru_cache
@@ -26,6 +27,9 @@ DEFAULT_LOCAL_SPACE = Path("multilingual_dict_20260603_122323")
26
  DEFAULT_LANGS = ["de", "en", "fr", "lb"]
27
  REQUIRED_FILES = ("aligned_all.faiss", "all_metadata.jsonl", "config.json")
28
  CACHE_DIR = Path(os.getenv("ARTIFACT_CACHE_DIR", "/tmp/multilingual_space_artifacts"))
 
 
 
29
 
30
 
31
  @dataclass
@@ -96,24 +100,21 @@ def make_s3_client():
96
  return boto3.client(**kwargs)
97
 
98
 
99
- def latest_artifact_uri(client) -> str:
100
  explicit = os.getenv("SPACE_ARTIFACT_S3_URI", "").strip().rstrip("/")
101
  if explicit:
102
- return explicit
103
 
104
  prefix_override = os.getenv("SPACE_ARTIFACT_S3_PREFIX", "").strip()
105
  prefix_uri = prefix_override or DEFAULT_ARTIFACT_PREFIX
106
  bucket, prefix = parse_s3_uri(prefix_uri)
107
  prefix = prefix.rstrip("/") + "/"
108
- pattern = re.compile(
109
- r"(.*multilingual_(?:dict|space)_(\d{8}_\d{6})(?:\.json)?)/config\.json$"
110
- )
111
  candidates: list[tuple[str, str]] = []
112
 
113
  paginator = client.get_paginator("list_objects_v2")
114
  for page in paginator.paginate(Bucket=bucket, Prefix=prefix):
115
  for obj in page.get("Contents", []):
116
- match = pattern.match(obj["Key"])
117
  if match:
118
  candidates.append((match.group(2), match.group(1)))
119
 
@@ -123,20 +124,74 @@ def latest_artifact_uri(client) -> str:
123
  )
124
 
125
  # Run ids are timestamps: YYYYMMDD_HHMMSS. Lexicographic sort gives newest run.
126
- run_id, key = sorted(candidates)[-1]
127
- uri = f"s3://{bucket}/{key}"
128
- print(f"Selected latest stage 6 artifact {run_id}: {uri}", file=sys.stderr)
 
 
 
129
  return uri
130
 
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  def local_cache_for_uri(uri: str) -> Path:
133
  _, key = parse_s3_uri(uri)
134
  return CACHE_DIR / Path(key.rstrip("/")).name
135
 
136
 
137
- def download_space_from_s3() -> tuple[Path, str]:
138
  client = make_s3_client()
139
- uri = latest_artifact_uri(client)
140
  local_dir = local_cache_for_uri(uri)
141
  local_dir.mkdir(parents=True, exist_ok=True)
142
 
@@ -153,7 +208,16 @@ def download_space_from_s3() -> tuple[Path, str]:
153
  return local_dir, uri
154
 
155
 
156
- def find_space_dir() -> tuple[Path, str]:
 
 
 
 
 
 
 
 
 
157
  local_override = os.getenv("SPACE_DIR", "").strip()
158
  if local_override:
159
  path = Path(local_override)
@@ -303,8 +367,8 @@ def build_lookup(languages: dict[str, LangVectors]) -> dict[str, dict[str, list[
303
 
304
 
305
  @lru_cache(maxsize=1)
306
- def load_space() -> Space:
307
- space_dir, artifact_uri = find_space_dir()
308
  config = read_config(space_dir)
309
  metadata, ids_by_lang = read_metadata(space_dir)
310
  vectors_by_lang = load_vectors_from_faiss(space_dir, ids_by_lang)
@@ -527,9 +591,10 @@ def translate_like_terminal(
527
  filter_stopwords: bool,
528
  filter_bad_tokens: bool,
529
  use_surface: bool,
 
530
  ) -> tuple[str, list[list[Any]]]:
531
  try:
532
- space = load_space()
533
  use_surface = bool(use_surface and space.has_surface_forms)
534
  opts = make_options(
535
  top_k,
@@ -617,17 +682,23 @@ def translate_like_terminal(
617
  return f"Error: {exc}", []
618
 
619
 
620
- def initialize() -> tuple[Any, ...]:
 
 
 
 
 
 
 
 
 
621
  try:
622
- space = load_space()
623
  opts = default_options(space.config)
624
  source_lang = space.config.get("pivot_lang", "de")
625
  if source_lang not in space.languages:
626
  source_lang = space.languages[0]
627
- status = (
628
- f"Loaded {space.artifact_uri} with "
629
- f"{sum(len(item.metas) for item in space.by_lang.values()):,} vectors."
630
- )
631
  return (
632
  status,
633
  gr.update(choices=space.languages, value=source_lang),
@@ -665,6 +736,40 @@ def initialize() -> tuple[Any, ...]:
665
  )
666
 
667
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
668
  CSS = """
669
  body { background: #f7f5ef; }
670
  .gradio-container { max-width: 1120px !important; }
@@ -681,6 +786,12 @@ with gr.Blocks(title="Multilingual Dictionary Explorer", css=CSS) as demo:
681
  elem_classes=["app-title"],
682
  )
683
  status = gr.Markdown("Loading artifacts...", elem_classes=["status"])
 
 
 
 
 
 
684
 
685
  with gr.Row():
686
  with gr.Column(scale=1, min_width=320):
@@ -808,14 +919,35 @@ with gr.Blocks(title="Multilingual Dictionary Explorer", css=CSS) as demo:
808
  filter_stopwords,
809
  filter_bad_tokens,
810
  use_surface,
 
811
  ]
812
  search.click(translate_like_terminal, inputs=inputs, outputs=[output_text, output_table])
813
  query.submit(translate_like_terminal, inputs=inputs, outputs=[output_text, output_table])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
814
 
815
  demo.load(
816
  initialize,
817
  outputs=[
818
  status,
 
819
  source_lang,
820
  top_k,
821
  min_score,
 
5
  import os
6
  import re
7
  import sys
8
+ import time
9
  import unicodedata
10
  from dataclasses import dataclass
11
  from functools import lru_cache
 
27
  DEFAULT_LANGS = ["de", "en", "fr", "lb"]
28
  REQUIRED_FILES = ("aligned_all.faiss", "all_metadata.jsonl", "config.json")
29
  CACHE_DIR = Path(os.getenv("ARTIFACT_CACHE_DIR", "/tmp/multilingual_space_artifacts"))
30
+ ARTIFACT_PATTERN = re.compile(
31
+ r"(.*multilingual_(?:dict|space)_(\d{8}_\d{6})(?:\.json)?)/config\.json$"
32
+ )
33
 
34
 
35
  @dataclass
 
100
  return boto3.client(**kwargs)
101
 
102
 
103
+ def list_s3_artifact_uris(client) -> list[str]:
104
  explicit = os.getenv("SPACE_ARTIFACT_S3_URI", "").strip().rstrip("/")
105
  if explicit:
106
+ return [explicit]
107
 
108
  prefix_override = os.getenv("SPACE_ARTIFACT_S3_PREFIX", "").strip()
109
  prefix_uri = prefix_override or DEFAULT_ARTIFACT_PREFIX
110
  bucket, prefix = parse_s3_uri(prefix_uri)
111
  prefix = prefix.rstrip("/") + "/"
 
 
 
112
  candidates: list[tuple[str, str]] = []
113
 
114
  paginator = client.get_paginator("list_objects_v2")
115
  for page in paginator.paginate(Bucket=bucket, Prefix=prefix):
116
  for obj in page.get("Contents", []):
117
+ match = ARTIFACT_PATTERN.match(obj["Key"])
118
  if match:
119
  candidates.append((match.group(2), match.group(1)))
120
 
 
124
  )
125
 
126
  # Run ids are timestamps: YYYYMMDD_HHMMSS. Lexicographic sort gives newest run.
127
+ return [f"s3://{bucket}/{key}" for _, key in sorted(candidates, reverse=True)]
128
+
129
+
130
+ def latest_artifact_uri(client) -> str:
131
+ uri = list_s3_artifact_uris(client)[0]
132
+ print(f"Selected latest stage 6 artifact: {uri}", file=sys.stderr)
133
  return uri
134
 
135
 
136
+ def local_artifact_uris() -> list[str]:
137
+ local_override = os.getenv("SPACE_DIR", "").strip()
138
+ if local_override:
139
+ path = Path(local_override)
140
+ if path.exists():
141
+ return [str(path)]
142
+
143
+ local_candidates = sorted(
144
+ [*Path(".").glob("multilingual_dict_*"), *Path(".").glob("multilingual_space_*.json")],
145
+ reverse=True,
146
+ )
147
+ if DEFAULT_LOCAL_SPACE.exists():
148
+ local_candidates = [
149
+ DEFAULT_LOCAL_SPACE,
150
+ *[path for path in local_candidates if path != DEFAULT_LOCAL_SPACE],
151
+ ]
152
+ return [str(path) for path in local_candidates]
153
+
154
+
155
+ def available_artifact_uris() -> list[str]:
156
+ local_uris = local_artifact_uris()
157
+ if local_uris:
158
+ return local_uris
159
+ return list_s3_artifact_uris(make_s3_client())
160
+
161
+
162
+ def artifact_name(uri: str) -> str:
163
+ uri = str(uri or "").strip().rstrip("/")
164
+ if not uri:
165
+ return "latest artifact"
166
+ if uri.startswith("s3://"):
167
+ parsed = urlparse(uri)
168
+ return Path(parsed.path).name or uri
169
+ return Path(uri).name or uri
170
+
171
+
172
+ def artifact_choices() -> tuple[list[tuple[str, str]], str]:
173
+ uris = available_artifact_uris()
174
+ choices = [
175
+ (f"{artifact_name(uri)}{' (default)' if index == 0 else ''}", uri)
176
+ for index, uri in enumerate(uris)
177
+ ]
178
+ return choices, uris[0]
179
+
180
+
181
+ def format_duration(seconds: float) -> str:
182
+ if seconds < 1:
183
+ return f"{seconds * 1000:.0f} ms"
184
+ return f"{seconds:.1f}s"
185
+
186
+
187
  def local_cache_for_uri(uri: str) -> Path:
188
  _, key = parse_s3_uri(uri)
189
  return CACHE_DIR / Path(key.rstrip("/")).name
190
 
191
 
192
+ def download_space_from_s3(uri: str | None = None) -> tuple[Path, str]:
193
  client = make_s3_client()
194
+ uri = uri or latest_artifact_uri(client)
195
  local_dir = local_cache_for_uri(uri)
196
  local_dir.mkdir(parents=True, exist_ok=True)
197
 
 
208
  return local_dir, uri
209
 
210
 
211
+ def find_space_dir(artifact_uri: str = "") -> tuple[Path, str]:
212
+ artifact_uri = str(artifact_uri or "").strip()
213
+ if artifact_uri:
214
+ if artifact_uri.startswith("s3://"):
215
+ return download_space_from_s3(artifact_uri)
216
+ path = Path(artifact_uri)
217
+ if path.exists():
218
+ return path, str(path)
219
+ raise FileNotFoundError(f"Selected artifact does not exist: {artifact_uri}")
220
+
221
  local_override = os.getenv("SPACE_DIR", "").strip()
222
  if local_override:
223
  path = Path(local_override)
 
367
 
368
 
369
  @lru_cache(maxsize=1)
370
+ def load_space(artifact_uri: str = "") -> Space:
371
+ space_dir, artifact_uri = find_space_dir(artifact_uri)
372
  config = read_config(space_dir)
373
  metadata, ids_by_lang = read_metadata(space_dir)
374
  vectors_by_lang = load_vectors_from_faiss(space_dir, ids_by_lang)
 
591
  filter_stopwords: bool,
592
  filter_bad_tokens: bool,
593
  use_surface: bool,
594
+ artifact_uri: str,
595
  ) -> tuple[str, list[list[Any]]]:
596
  try:
597
+ space = load_space(artifact_uri)
598
  use_surface = bool(use_surface and space.has_surface_forms)
599
  opts = make_options(
600
  top_k,
 
682
  return f"Error: {exc}", []
683
 
684
 
685
+ def loaded_status(space: Space, elapsed: float) -> str:
686
+ total_vectors = sum(len(item.metas) for item in space.by_lang.values())
687
+ return (
688
+ f"Loaded {artifact_name(space.artifact_uri)} in {format_duration(elapsed)} "
689
+ f"with {total_vectors:,} vectors."
690
+ )
691
+
692
+
693
+ def load_artifact_settings(artifact_uri: str) -> tuple[Any, ...]:
694
+ start = time.perf_counter()
695
  try:
696
+ space = load_space(artifact_uri)
697
  opts = default_options(space.config)
698
  source_lang = space.config.get("pivot_lang", "de")
699
  if source_lang not in space.languages:
700
  source_lang = space.languages[0]
701
+ status = loaded_status(space, time.perf_counter() - start)
 
 
 
702
  return (
703
  status,
704
  gr.update(choices=space.languages, value=source_lang),
 
736
  )
737
 
738
 
739
+ def initialize() -> tuple[Any, ...]:
740
+ try:
741
+ choices, artifact_uri = artifact_choices()
742
+ settings = load_artifact_settings(artifact_uri)
743
+ return (
744
+ settings[0],
745
+ gr.update(choices=choices, value=artifact_uri),
746
+ *settings[1:],
747
+ )
748
+ except Exception as exc:
749
+ return (
750
+ f"Load error: {exc}",
751
+ gr.update(choices=[], value=None),
752
+ gr.update(choices=DEFAULT_LANGS, value="de"),
753
+ 3,
754
+ 0.15,
755
+ 10,
756
+ 9,
757
+ 50,
758
+ True,
759
+ gr.update(
760
+ value=False,
761
+ interactive=False,
762
+ label="show surface forms (no aligned space loaded)",
763
+ ),
764
+ )
765
+
766
+
767
+ def mark_artifact_loading(artifact_uri: str) -> tuple[str, str, list[list[Any]]]:
768
+ load_space.cache_clear()
769
+ gc.collect()
770
+ return f"Loading {artifact_name(artifact_uri)}...", "", []
771
+
772
+
773
  CSS = """
774
  body { background: #f7f5ef; }
775
  .gradio-container { max-width: 1120px !important; }
 
786
  elem_classes=["app-title"],
787
  )
788
  status = gr.Markdown("Loading artifacts...", elem_classes=["status"])
789
+ artifact_version = gr.Dropdown(
790
+ label="Dictionary artifact",
791
+ choices=[],
792
+ value=None,
793
+ interactive=True,
794
+ )
795
 
796
  with gr.Row():
797
  with gr.Column(scale=1, min_width=320):
 
919
  filter_stopwords,
920
  filter_bad_tokens,
921
  use_surface,
922
+ artifact_version,
923
  ]
924
  search.click(translate_like_terminal, inputs=inputs, outputs=[output_text, output_table])
925
  query.submit(translate_like_terminal, inputs=inputs, outputs=[output_text, output_table])
926
+ artifact_version.change(
927
+ mark_artifact_loading,
928
+ inputs=[artifact_version],
929
+ outputs=[status, output_text, output_table],
930
+ ).then(
931
+ load_artifact_settings,
932
+ inputs=[artifact_version],
933
+ outputs=[
934
+ status,
935
+ source_lang,
936
+ top_k,
937
+ min_score,
938
+ csls_k,
939
+ candidate_retrieval_k,
940
+ csls_prefetch_k,
941
+ bidirectional,
942
+ use_surface,
943
+ ],
944
+ ).then(translate_like_terminal, inputs=inputs, outputs=[output_text, output_table])
945
 
946
  demo.load(
947
  initialize,
948
  outputs=[
949
  status,
950
+ artifact_version,
951
  source_lang,
952
  top_k,
953
  min_score,