|
11 | 11 | import logging |
12 | 12 | import os |
13 | 13 | import re |
| 14 | +from concurrent.futures import ThreadPoolExecutor |
14 | 15 | from pathlib import Path |
15 | 16 | from typing import Any, Dict, List, Optional, Tuple, Union |
16 | 17 |
|
@@ -114,6 +115,28 @@ def get_available_proteingym_datasets( |
114 | 115 | return filtered_metadata |
115 | 116 |
|
116 | 117 |
|
| 118 | +def _parse_embedding_columns_inplace(df: pd.DataFrame) -> pd.DataFrame: |
| 119 | + """Parse embedding columns from JSON strings to numpy arrays in-place. |
| 120 | +
|
| 121 | + Modifies embedding columns in-place to avoid memory duplication. |
| 122 | +
|
| 123 | + Args: |
| 124 | + df: DataFrame with potential embedding columns as JSON strings |
| 125 | +
|
| 126 | + Returns: |
| 127 | + The same DataFrame with parsed embeddings (modified in-place) |
| 128 | + """ |
| 129 | + for col in df.columns: |
| 130 | + if col == "embedding" or col.startswith("embedding_layer_"): |
| 131 | + if len(df) > 0 and isinstance(df[col].iloc[0], str): |
| 132 | + # Parse in-place to avoid memory duplication |
| 133 | + for idx in df.index: |
| 134 | + val = df.at[idx, col] |
| 135 | + if isinstance(val, str): |
| 136 | + df.at[idx, col] = np.array(json.loads(val)) |
| 137 | + return df |
| 138 | + |
| 139 | + |
117 | 140 | def try_load_sharded_embedding_file(embeddings_dir: str, prefix: str) -> pd.DataFrame: |
118 | 141 | """Load sharded embedding files matching pattern <prefix>-(\d+)_of_(\d+).csv. |
119 | 142 |
|
@@ -183,14 +206,26 @@ def try_load_sharded_embedding_file(embeddings_dir: str, prefix: str) -> pd.Data |
183 | 206 | if max_idx > expected_total_shards: |
184 | 207 | raise ValueError(f"Shard index {max_idx} exceeds total shard count {expected_total_shards}") |
185 | 208 |
|
186 | | - # Load and concatenate all shards in order |
| 209 | + # Load and concatenate all shards in parallel |
187 | 210 | logger.info(f"Loading {expected_total_shards} sharded embedding files for {prefix}") |
188 | | - shard_dfs = [] |
189 | | - for idx in range(1, expected_total_shards + 1): |
| 211 | + |
| 212 | + def load_and_parse_shard(idx: int) -> pd.DataFrame: |
190 | 213 | filepath = shard_info[idx][1] |
191 | 214 | shard_df = pd.read_csv(filepath) |
192 | | - shard_dfs.append(shard_df) |
193 | | - logger.info(f"Loaded shard {idx}/{expected_total_shards} with {len(shard_df)} rows") |
| 215 | + _parse_embedding_columns_inplace(shard_df) |
| 216 | + logger.info( |
| 217 | + f"Loaded and parsed shard {idx}/{expected_total_shards} with {len(shard_df)} rows" |
| 218 | + ) |
| 219 | + return shard_df |
| 220 | + |
| 221 | + with ThreadPoolExecutor(max_workers=4) as executor: |
| 222 | + # Submit all shard loading tasks |
| 223 | + futures = { |
| 224 | + idx: executor.submit(load_and_parse_shard, idx) |
| 225 | + for idx in range(1, expected_total_shards + 1) |
| 226 | + } |
| 227 | + # Collect results in order |
| 228 | + shard_dfs = [futures[idx].result() for idx in range(1, expected_total_shards + 1)] |
194 | 229 |
|
195 | 230 | return pd.concat(shard_dfs, ignore_index=True) |
196 | 231 |
|
@@ -228,8 +263,9 @@ def get_proteingym_dataset( |
228 | 263 | if os.path.exists(embedding_file_path): |
229 | 264 | # Single file case |
230 | 265 | embedding_df = pd.read_csv(embedding_file_path) |
| 266 | + _parse_embedding_columns_inplace(embedding_df) |
231 | 267 | else: |
232 | | - # Try loading sharded files |
| 268 | + # Try loading sharded files (parsing happens inside) |
233 | 269 | prefix = f"{dms_id}_embedding_{embedding_model_id}" |
234 | 270 | try: |
235 | 271 | embedding_df = try_load_sharded_embedding_file(str(EMBEDDINGS_DIR), prefix) |
@@ -398,16 +434,7 @@ def maybe_convert_seq_id_to_seq(seq_id: str): |
398 | 434 | category_df = category_df.reindex(activity_df.index) |
399 | 435 | category_df = category_df.fillna(False) |
400 | 436 |
|
401 | | - # Convert embedding column from string to numpy array if needed |
402 | | - for col in embedding_df.columns: |
403 | | - if col == "embedding" or col.startswith("embedding_layer_"): |
404 | | - if isinstance(embedding_df[col].iloc[0], str): |
405 | | - # embedding_df["embedding"] = embedding_df["embedding"].apply( |
406 | | - # lambda x: np.array(ast.literal_eval(x)) if isinstance(x, str) else x |
407 | | - # ) |
408 | | - embedding_df[col] = embedding_df[col].apply( |
409 | | - lambda x: np.array(json.loads(x)) if isinstance(x, str) else x |
410 | | - ) |
| 437 | + # Embeddings are already parsed during loading (via _parse_embedding_columns_inplace) |
411 | 438 |
|
412 | 439 | # We lose ordering with the set operations but recover it with a sort later. |
413 | 440 | logging.info( |
|
0 commit comments