HCAI-Lab/w2-consensus-deepdive-unlearning-artifacts / social-data-attribution-w2 /scripts /modal /cache_corpus_shards.py
| """One-time download of all source shards from R2 to a persistent Modal volume. | |
| Fans out workers to download source shards in parallel. Each worker handles | |
| a chunk of shards and writes them to a shared volume preserving the original | |
| R2 key structure. Workers skip files that already exist (idempotent). | |
| Usage: | |
| uv run modal run scripts/modal/cache_corpus_shards.py --chunk-count 256 | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import os | |
| import time | |
| from pathlib import Path | |
| import modal | |
| from config import ( | |
| R2_BUCKET, | |
| R2_ENDPOINT_URL, | |
| R2_INPUT_PREFIXES, | |
| R2_SECRET_NAME, | |
| ) | |
| logger = logging.getLogger("cache_corpus_shards") | |
| _local_path = Path(__file__).resolve() | |
| if len(_local_path.parents) > 2: | |
| _REPO_ROOT = _local_path.parents[2] | |
| _SRC_ROOT = str(_REPO_ROOT / "src") | |
| _CONFIG_PY = str(_REPO_ROOT / "scripts" / "modal" / "config.py") | |
| _cache_image = ( | |
| modal.Image.debian_slim(python_version="3.12") | |
| .pip_install("boto3>=1.37.0") | |
| .env({"PYTHONPATH": "/root/src:/root"}) | |
| .add_local_file(_CONFIG_PY, remote_path="/root/config.py", copy=True) | |
| .add_local_dir(_SRC_ROOT, remote_path="/root/src", copy=True) | |
| ) | |
| else: | |
| _cache_image = modal.Image.debian_slim(python_version="3.12") | |
| app = modal.App("soc134-cache-corpus") | |
| r2_secret = modal.Secret.from_name(R2_SECRET_NAME) | |
| corpus_volume = modal.Volume.from_name("soc134-corpus-cache", create_if_missing=True) | |
| CORPUS_MOUNT = "/corpus" | |
| WORKER_TIMEOUT = 7200 | |
| WORKER_CPU = 2 | |
| WORKER_MEMORY = 8192 | |
| def _ensure_r2_env() -> None: | |
| env_aliases = { | |
| "R2_ACCESS_KEY_ID": ("R2_ACCESS_KEY_ID", "AWS_ACCESS_KEY_ID", "access_key_id"), | |
| "R2_SECRET_ACCESS_KEY": ( | |
| "R2_SECRET_ACCESS_KEY", | |
| "AWS_SECRET_ACCESS_KEY", | |
| "secret_access_key", | |
| ), | |
| } | |
| for target, aliases in env_aliases.items(): | |
| if target in os.environ and os.environ[target]: | |
| continue | |
| for alias in aliases: | |
| value = os.environ.get(alias) | |
| if value: | |
| os.environ[target] = value | |
| break | |
| if target not in os.environ: | |
| raise KeyError(target) | |
| if "R2_ENDPOINT_URL" not in os.environ: | |
| os.environ["R2_ENDPOINT_URL"] = R2_ENDPOINT_URL | |
| if "R2_BUCKET" not in os.environ: | |
| os.environ["R2_BUCKET"] = R2_BUCKET | |
| def _list_all_source_keys(client, bucket: str) -> list[str]: | |
| keys: list[str] = [] | |
| for prefix in R2_INPUT_PREFIXES: | |
| normalized = prefix.rstrip("/") + "/" | |
| paginator = client.get_paginator("list_objects_v2") | |
| for page in paginator.paginate(Bucket=bucket, Prefix=normalized): | |
| for item in page.get("Contents", []): | |
| key = item["Key"] | |
| if key.endswith(".jsonl.zst"): | |
| keys.append(key) | |
| return sorted(set(keys)) | |
| def _slice_for_chunk(items: list[str], chunk_index: int, chunk_count: int) -> list[str]: | |
| chunk_size = len(items) // chunk_count | |
| remainder = len(items) % chunk_count | |
| start = chunk_index * chunk_size + min(chunk_index, remainder) | |
| end = start + chunk_size + (1 if chunk_index < remainder else 0) | |
| return items[start:end] | |
| def list_source_shards() -> list[str]: | |
| _ensure_r2_env() | |
| import boto3 | |
| client = boto3.client( | |
| "s3", | |
| endpoint_url=R2_ENDPOINT_URL, | |
| aws_access_key_id=os.environ["R2_ACCESS_KEY_ID"], | |
| aws_secret_access_key=os.environ["R2_SECRET_ACCESS_KEY"], | |
| region_name="auto", | |
| ) | |
| return _list_all_source_keys(client, R2_BUCKET) | |
| def cache_chunk( | |
| shard_keys: list[str], | |
| chunk_index: int, | |
| ) -> dict[str, object]: | |
| t0 = time.monotonic() | |
| _ensure_r2_env() | |
| import boto3 | |
| client = boto3.client( | |
| "s3", | |
| endpoint_url=R2_ENDPOINT_URL, | |
| aws_access_key_id=os.environ["R2_ACCESS_KEY_ID"], | |
| aws_secret_access_key=os.environ["R2_SECRET_ACCESS_KEY"], | |
| region_name="auto", | |
| ) | |
| downloaded = 0 | |
| skipped = 0 | |
| failed = 0 | |
| total_bytes = 0 | |
| errors: list[str] = [] | |
| for i, key in enumerate(shard_keys): | |
| dest = Path(CORPUS_MOUNT) / key | |
| if dest.exists() and dest.stat().st_size > 0: | |
| skipped += 1 | |
| continue | |
| max_retries = 3 | |
| for attempt in range(max_retries): | |
| try: | |
| response = client.get_object(Bucket=R2_BUCKET, Key=key) | |
| data = response["Body"].read() | |
| dest.parent.mkdir(parents=True, exist_ok=True) | |
| tmp = dest.with_suffix(".tmp") | |
| tmp.write_bytes(data) | |
| tmp.rename(dest) | |
| downloaded += 1 | |
| total_bytes += len(data) | |
| break | |
| except Exception as exc: | |
| if attempt == max_retries - 1: | |
| error_msg = f"{key}: {type(exc).__name__}: {exc}" | |
| errors.append(error_msg) | |
| failed += 1 | |
| logger.error("Failed shard %s: %s", key, error_msg) | |
| else: | |
| wait = (attempt + 1) * 5 | |
| time.sleep(wait) | |
| if (i + 1) % 50 == 0: | |
| corpus_volume.commit() | |
| logger.info( | |
| "Chunk %d progress: %d/%d (downloaded=%d, skipped=%d, failed=%d)", | |
| chunk_index, | |
| i + 1, | |
| len(shard_keys), | |
| downloaded, | |
| skipped, | |
| failed, | |
| ) | |
| corpus_volume.commit() | |
| elapsed = time.monotonic() - t0 | |
| return { | |
| "chunk_index": chunk_index, | |
| "total_shards": len(shard_keys), | |
| "downloaded": downloaded, | |
| "skipped": skipped, | |
| "failed": failed, | |
| "total_bytes": total_bytes, | |
| "elapsed_seconds": round(elapsed, 1), | |
| "errors": errors[:10], | |
| } | |
| def main( | |
| chunk_count: int = 256, | |
| dry_run: bool = False, | |
| ) -> None: | |
| print("Listing all source shards from R2...") | |
| all_keys = list_source_shards.remote() | |
| print( | |
| f"Found {len(all_keys):,} source shards across {len(R2_INPUT_PREFIXES)} prefixes" | |
| ) | |
| if dry_run: | |
| print("Dry run - not downloading") | |
| return | |
| chunks = [_slice_for_chunk(all_keys, i, chunk_count) for i in range(chunk_count)] | |
| non_empty = [(keys, i) for i, keys in enumerate(chunks) if keys] | |
| print(f"Launching {len(non_empty)} workers (chunk_count={chunk_count})...") | |
| results = list(cache_chunk.starmap(non_empty)) | |
| total_downloaded = sum(r.get("downloaded", 0) for r in results) | |
| total_skipped = sum(r.get("skipped", 0) for r in results) | |
| total_failed = sum(r.get("failed", 0) for r in results) | |
| total_bytes = sum(r.get("total_bytes", 0) for r in results) | |
| print("\nCorpus cache results:") | |
| print(f" Total shards: {len(all_keys):,}") | |
| print(f" Downloaded: {total_downloaded:,}") | |
| print(f" Skipped (exist): {total_skipped:,}") | |
| print(f" Failed: {total_failed:,}") | |
| print(f" Bytes written: {total_bytes / (1024**3):.1f} GB") | |
| if total_failed > 0: | |
| failed_results = [r for r in results if r.get("failed", 0) > 0] | |
| for r in failed_results[:5]: | |
| for err in r.get("errors", [])[:3]: | |
| print(f" Error: {err}") | |
Xet Storage Details
- Size:
- 7.62 kB
- Xet hash:
- 3522cd144f0f95d26d99f10051729852b4c6bae07876a58259cbb39a8eff8a1e
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.