lukhsaankumar commited on
Commit
52c0a32
·
1 Parent(s): 9fd7a87

Deploy DeepFake Detector API - 2026-04-20 23:04:30

Browse files
COLD_START_OPTIMIZATION.md CHANGED
@@ -50,11 +50,10 @@ Note:
50
 
51
  ## Current Bottlenecks
52
 
53
- 1. Runtime model download during startup from Hugging Face Hub.
54
- 2. Sequential submodel loading in model registry.
55
- 3. Startup gap before model load logs (from 04:24:02 to 04:25:15) that should be instrumented for precise attribution.
56
- 4. Environment issue: libgomp reports invalid OMP_NUM_THREADS value.
57
- 5. Model compatibility warning: scikit-learn pickle version mismatch at startup.
58
 
59
  ## Implementation Plan
60
 
@@ -216,34 +215,66 @@ startup_dt = time.perf_counter() - startup_t0
216
  logger.info(f"Startup total duration_seconds={startup_dt:.3f}")
217
  ```
218
 
219
- ## Phase 4: Runtime Hygiene (Low Effort, Prevent Hidden Slowdowns)
220
 
221
- ### 4.1 Fix OMP setting warning
 
222
 
223
- Target file: start.sh
 
 
 
224
 
225
- Add a valid default:
 
 
 
 
 
 
226
 
227
- ```bash
228
- export OMP_NUM_THREADS="${OMP_NUM_THREADS:-1}"
229
- ```
230
 
231
- This removes:
232
- - libgomp: Invalid value for environment variable OMP_NUM_THREADS
233
 
234
- ### 4.2 Pin scikit-learn to training-compatible version
 
235
 
236
- Target file: requirements.txt
 
237
 
238
- Observed warning indicates model pickle was produced with 1.6.1 while runtime uses 1.8.0.
 
 
 
239
 
240
- Pin:
 
 
241
 
242
- ```text
243
- scikit-learn==1.6.1
244
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
 
246
- This is not directly a speed optimization, but it removes compatibility risk during cold start model deserialization.
 
 
247
 
248
  ## Validation and Benchmark Protocol
249
 
@@ -306,16 +337,51 @@ Source log window:
306
  - End-to-end startup remained dominated by pre-lifespan/init time (98s still much larger than model load slice).
307
  - Runtime hygiene warnings no longer appeared in this run (no OMP warning and no sklearn pickle version warning).
308
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  ## Comparison Template (Fill After Implementation)
310
 
311
- | Metric | Baseline (2026-04-20) | After Phase 1 | After Phase 2 | Final |
312
- |---|---:|---:|---:|---:|
313
- | Queue/build to app startup | 28s | 36s | 119s | |
314
- | App startup to model-ready | 94s | 99s | 98s | |
315
- | API model load phase | 21s | 5s | 4s | |
316
- | vit-base load | 13s | 1s | 2s | |
317
- | deit-distilled load | 5s | 2s | 2s | |
318
- | Total visible build timed stages | 20.4s | 28.0s | 112.7s | |
 
 
319
 
320
  ## Expected Outcome
321
 
 
50
 
51
  ## Current Bottlenecks
52
 
53
+ 1. Dominant pre-app startup delay before Python module import begins.
54
+ 2. Build-time prefetch cost when cache layers miss (extra build wall time).
55
+ 3. Model loading is no longer dominant (~4s with current cache and bounded parallel load).
56
+ 4. Cold-start variance likely includes platform scheduling/provisioning overhead.
 
57
 
58
  ## Implementation Plan
59
 
 
215
  logger.info(f"Startup total duration_seconds={startup_dt:.3f}")
216
  ```
217
 
218
+ ## Phase 4: Use Persistent Storage Cache (/data)
219
 
220
+ Goal:
221
+ - Make /data the primary cache location so model artifacts survive container rebuilds/restarts.
222
 
223
+ Target files:
224
+ - app/core/config.py
225
+ - start.sh
226
+ - app/services/hf_hub_service.py
227
 
228
+ Plan:
229
+ 1. Prefer /data-backed cache paths when available:
230
+ - HF_HOME=/data/.cache/huggingface
231
+ - HF_CACHE_DIR=/data/.hf_cache
232
+ 2. Keep fallback to /app/.hf_cache when /data is unavailable.
233
+ 3. Ensure startup creates/chowns cache directories safely.
234
+ 4. Keep cache-hit logging so verification remains explicit in logs.
235
 
236
+ Expected impact:
237
+ - Faster warm boots across deploys.
238
+ - Lower risk of repeated network fetch for large model files.
239
 
240
+ ## Phase 5: Decouple Build From Prefetch
 
241
 
242
+ Goal:
243
+ - Reduce rebuild penalty from model prefetch while keeping runtime fast.
244
 
245
+ Target file:
246
+ - Dockerfile
247
 
248
+ Plan:
249
+ 1. Make build-time prefetch optional via ARG/ENV flag.
250
+ 2. Default to skipping build prefetch when persistent /data cache is enabled.
251
+ 3. Keep one-time warm path at runtime (guarded by cache/sentinel file in /data).
252
 
253
+ Expected impact:
254
+ - Faster image rebuild/push cycles.
255
+ - Better developer iteration speed without sacrificing warmed production startup.
256
 
257
+ ## Phase 6: Platform/GPU Startup Characterization
258
+
259
+ Goal:
260
+ - Quantify how much of remaining cold start is platform provisioning vs app code.
261
+
262
+ Plan:
263
+ 1. Run repeated cold starts with identical image on current T4.
264
+ 2. If available, test one higher-tier GPU Space and compare only phase3 markers.
265
+ 3. Record variance in:
266
+ - Application Startup -> module_import_start
267
+ - module_import_complete -> startup complete
268
+
269
+ Notes:
270
+ - GPU type can affect model initialization time.
271
+ - The measured dominant delay currently occurs before app module import, so platform scheduling/provisioning is likely the bigger lever than model code tuning.
272
+
273
+ ## Phase 7: Runtime Hygiene (Completed)
274
 
275
+ Completed changes:
276
+ 1. Set valid OMP default in start.sh.
277
+ 2. Pin scikit-learn to 1.6.1 for pickle compatibility.
278
 
279
  ## Validation and Benchmark Protocol
280
 
 
337
  - End-to-end startup remained dominated by pre-lifespan/init time (98s still much larger than model load slice).
338
  - Runtime hygiene warnings no longer appeared in this run (no OMP warning and no sklearn pickle version warning).
339
 
340
+ ## Phase 3 Results and Bottleneck Attribution
341
+
342
+ Source log window:
343
+ - Build queued at 2026-04-20 06:01:57
344
+ - Application startup begins at 2026-04-20 06:02:56
345
+ - Models loaded successfully at 2026-04-20 06:04:37
346
+
347
+ ### Phase 3 Timing Summary
348
+
349
+ | Segment | Start | End | Duration | Notes |
350
+ |---|---:|---:|---:|---|
351
+ | Queue/build to app startup | 06:01:57 | 06:02:56 | 59s | Includes scheduling, build finalization, image start |
352
+ | App startup to model-ready | 06:02:56 | 06:04:37 | 101s | End-to-end startup from Space startup marker |
353
+ | API model load phase | 06:04:33 | 06:04:37 | 4s | From app startup handler to models loaded |
354
+
355
+ ### Phase 3 Instrumentation Breakdown (Container Runtime)
356
+
357
+ | Marker | Duration |
358
+ |---|---:|
359
+ | module_import_complete | 4.050s |
360
+ | startup_model_load_duration_seconds | 3.967s |
361
+ | startup_lifespan_total_duration_seconds | 3.967s |
362
+ | load_from_fusion_repo_total_duration_seconds | 3.967s |
363
+
364
+ ### Bottleneck Attribution
365
+
366
+ - Dominant gap is before module import:
367
+ - 06:02:56 (Application Startup) -> 06:04:29 (module_import_start) = 93s.
368
+ - App code after import is no longer the main problem:
369
+ - import + lifespan + model load is about 8s total.
370
+ - Conclusion:
371
+ - Remaining cold start is primarily platform/container readiness overhead, not model download/load logic.
372
+
373
  ## Comparison Template (Fill After Implementation)
374
 
375
+ | Metric | Baseline (2026-04-20) | After Phase 1 | After Phase 2 | After Phase 3 | Final |
376
+ |---|---:|---:|---:|---:|---:|
377
+ | Queue/build to app startup | 28s | 36s | 119s | 59s | |
378
+ | App startup to model-ready | 94s | 99s | 98s | 101s | |
379
+ | API model load phase | 21s | 5s | 4s | 4s | |
380
+ | vit-base load | 13s | 1s | 2s | 2s | |
381
+ | deit-distilled load | 5s | 2s | 2s | 2s | |
382
+ | Total visible build timed stages | 20.4s | 28.0s | 112.7s | 33.6s | |
383
+ | Phase3 module import duration | n/a | n/a | n/a | 4.050s | |
384
+ | Phase3 model registry total duration | n/a | n/a | n/a | 3.967s | |
385
 
386
  ## Expected Outcome
387
 
README.md CHANGED
@@ -56,7 +56,10 @@ Use [backend/.env.example](.env.example) as the source of truth.
56
  Common runtime variables:
57
 
58
  - `HF_FUSION_REPO_ID` (default: `DeepFakeDetector/fusion-logreg-final`)
59
- - `HF_CACHE_DIR` (default: `.hf_cache`)
 
 
 
60
  - `HF_TOKEN` (optional; required for private model repos or non-interactive HF auth)
61
  - `GOOGLE_API_KEY` (optional; required for Gemini explanations)
62
  - `HOST` (default: `0.0.0.0`)
@@ -70,6 +73,7 @@ HF Spaces deploy variables (used by [backend/deploy-to-hf.sh](deploy-to-hf.sh)):
70
  - `HF_SPACE_WEB_URL`
71
  - `HF_SPACE_APP_URL`
72
  - `HF_DEPLOY_DIR`
 
73
 
74
  ## API Endpoints
75
 
@@ -109,17 +113,69 @@ bash ./backend/deploy-to-hf.sh
109
 
110
  The script will:
111
 
 
112
  - install Hugging Face CLI if needed
113
  - prompt/authenticate with HF (`hf auth login`) when required
114
  - clone Space repo into a separate temp deploy directory
115
  - copy backend files as-is (single `Dockerfile` setup)
116
  - commit and push to the HF Space
117
 
 
 
 
 
118
  After deploy, set Space secrets in Hugging Face:
119
 
120
  - `GOOGLE_API_KEY` (if using explanation endpoints)
121
  - `CORS_ORIGINS` (frontend domains)
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  ## Deploy to Railway
124
 
125
  - Set service root to `backend`
@@ -138,19 +194,12 @@ After deploy, set Space secrets in Hugging Face:
138
  ```text
139
  backend/
140
  ├── app/
 
141
  ├── tests/
142
  ├── Dockerfile
143
  ├── deploy-to-hf.sh
144
- ├── deploy-to-hf.ps1
145
- ├── requirements.txt
146
- └── README.md
147
- ```
148
-
149
- ## License
150
-
151
- MIT
152
- ├── deploy-to-hf.sh
153
- ├── deploy-to-hf.ps1
154
  ├── requirements.txt
155
  └── README.md
156
  ```
 
56
  Common runtime variables:
57
 
58
  - `HF_FUSION_REPO_ID` (default: `DeepFakeDetector/fusion-logreg-final`)
59
+ - `HF_CACHE_DIR` (default: `/app/.hf_cache`, auto-switched to `/data/.hf_cache` on Spaces when mounted)
60
+ - `HF_HOME` (default: `/app/.cache/huggingface`, auto-switched to `/data/.cache/huggingface` on Spaces)
61
+ - `HF_PERSISTENT_CACHE` (default: `true`)
62
+ - `HF_PERSISTENT_CACHE_ROOT` (default: `/data`)
63
  - `HF_TOKEN` (optional; required for private model repos or non-interactive HF auth)
64
  - `GOOGLE_API_KEY` (optional; required for Gemini explanations)
65
  - `HOST` (default: `0.0.0.0`)
 
73
  - `HF_SPACE_WEB_URL`
74
  - `HF_SPACE_APP_URL`
75
  - `HF_DEPLOY_DIR`
76
+ - `HF_BUCKET_SYNC_ON_DEPLOY` (default: `true`)
77
 
78
  ## API Endpoints
79
 
 
113
 
114
  The script will:
115
 
116
+ - sync `backend/data` to your HF bucket first (when configured)
117
  - install Hugging Face CLI if needed
118
  - prompt/authenticate with HF (`hf auth login`) when required
119
  - clone Space repo into a separate temp deploy directory
120
  - copy backend files as-is (single `Dockerfile` setup)
121
  - commit and push to the HF Space
122
 
123
+ If bucket sync should be skipped for a deploy, set:
124
+
125
+ - `HF_BUCKET_SYNC_ON_DEPLOY=false`
126
+
127
  After deploy, set Space secrets in Hugging Face:
128
 
129
  - `GOOGLE_API_KEY` (if using explanation endpoints)
130
  - `CORS_ORIGINS` (frontend domains)
131
 
132
+ ## Persistent Bucket Cache on Spaces
133
+
134
+ The backend now prefers persistent cache paths when a bucket is mounted read/write at `/data`.
135
+
136
+ Recommended bucket mount settings in Space:
137
+
138
+ - Mount path: `/data`
139
+ - Access mode: `Read & Write`
140
+
141
+ At runtime, `start.sh` will auto-select:
142
+
143
+ - `HF_HOME=/data/.cache/huggingface`
144
+ - `HF_CACHE_DIR=/data/.hf_cache`
145
+
146
+ If `/data` is unavailable, it falls back to `/app/.cache/huggingface` and `/app/.hf_cache`.
147
+
148
+ ## Bucket Upload / Sync (Reproducible)
149
+
150
+ Set these optional values in `backend/.env`:
151
+
152
+ - `HF_BUCKET_URI=hf://buckets/lukhsaankumar/DeepFakeDetectorBackend-storage`
153
+ - `HF_BUCKET_LOCAL_DIR=./data`
154
+ - `HF_BUCKET_DELETE=false`
155
+ - `HF_BUCKET_SYNC_ON_DEPLOY=true`
156
+
157
+ Bash:
158
+
159
+ ```bash
160
+ cd backend
161
+ chmod +x ./sync-bucket.sh
162
+ ./sync-bucket.sh # uses HF_BUCKET_LOCAL_DIR or ./data
163
+ ./sync-bucket.sh ./data # explicit local path
164
+ ```
165
+
166
+ PowerShell:
167
+
168
+ ```powershell
169
+ cd backend
170
+ ./sync-bucket.ps1 # uses HF_BUCKET_LOCAL_DIR or ./data
171
+ ./sync-bucket.ps1 .\data # explicit local path
172
+ ```
173
+
174
+ Notes:
175
+
176
+ - Scripts require authenticated HF CLI (`hf auth login`).
177
+ - Set `HF_BUCKET_DELETE=true` to mirror local to remote (deletes remote files not present locally).
178
+
179
  ## Deploy to Railway
180
 
181
  - Set service root to `backend`
 
194
  ```text
195
  backend/
196
  ├── app/
197
+ ├── data/
198
  ├── tests/
199
  ├── Dockerfile
200
  ├── deploy-to-hf.sh
201
+ ├── sync-bucket.sh
202
+ ├── sync-bucket.ps1
 
 
 
 
 
 
 
 
203
  ├── requirements.txt
204
  └── README.md
205
  ```
app/core/config.py CHANGED
@@ -8,6 +8,20 @@ from pydantic_settings import BaseSettings
8
  from typing import Optional
9
 
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  class Settings(BaseSettings):
12
  """Application settings loaded from environment variables."""
13
 
@@ -16,7 +30,10 @@ class Settings(BaseSettings):
16
  # - DeepFakeDetector/fusion-logreg-final (Logistic Regression - default)
17
  # - DeepFakeDetector/fusion-meta-final (Meta-classifier)
18
  HF_FUSION_REPO_ID: str = "DeepFakeDetector/fusion-logreg-final"
19
- HF_CACHE_DIR: str = ".hf_cache"
 
 
 
20
  HF_TOKEN: Optional[str] = None
21
 
22
  # Google Gemini API configuration
 
8
  from typing import Optional
9
 
10
 
11
+ def _default_hf_cache_dir() -> str:
12
+ """Prefer persistent storage on HF Spaces when available."""
13
+ if os.path.isdir("/data"):
14
+ return "/data/.hf_cache"
15
+ return "/app/.hf_cache"
16
+
17
+
18
+ def _default_hf_home() -> str:
19
+ """Prefer persistent Hugging Face home on HF Spaces when available."""
20
+ if os.path.isdir("/data"):
21
+ return "/data/.cache/huggingface"
22
+ return "/app/.cache/huggingface"
23
+
24
+
25
  class Settings(BaseSettings):
26
  """Application settings loaded from environment variables."""
27
 
 
30
  # - DeepFakeDetector/fusion-logreg-final (Logistic Regression - default)
31
  # - DeepFakeDetector/fusion-meta-final (Meta-classifier)
32
  HF_FUSION_REPO_ID: str = "DeepFakeDetector/fusion-logreg-final"
33
+ HF_CACHE_DIR: str = _default_hf_cache_dir()
34
+ HF_HOME: str = _default_hf_home()
35
+ HF_PERSISTENT_CACHE: bool = True
36
+ HF_PERSISTENT_CACHE_ROOT: str = "/data"
37
  HF_TOKEN: Optional[str] = None
38
 
39
  # Google Gemini API configuration
app/services/hf_hub_service.py CHANGED
@@ -38,6 +38,10 @@ class HFHubService:
38
  """
39
  self.cache_dir = cache_dir or settings.HF_CACHE_DIR
40
  self.token = token or settings.HF_TOKEN
 
 
 
 
41
 
42
  # Ensure cache directory exists
43
  Path(self.cache_dir).mkdir(parents=True, exist_ok=True)
 
38
  """
39
  self.cache_dir = cache_dir or settings.HF_CACHE_DIR
40
  self.token = token or settings.HF_TOKEN
41
+
42
+ # Keep Hugging Face process-wide cache settings aligned with app config.
43
+ os.environ.setdefault("HF_HOME", settings.HF_HOME)
44
+ os.environ.setdefault("HF_HUB_CACHE", self.cache_dir)
45
 
46
  # Ensure cache directory exists
47
  Path(self.cache_dir).mkdir(parents=True, exist_ok=True)
data/README.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Backend Bucket Data
2
+
3
+ This folder is the local source for bucket uploads via:
4
+
5
+ - `./sync-bucket.sh`
6
+ - `./sync-bucket.ps1`
7
+ - automatic pre-deploy sync from `deploy-to-hf.sh`
8
+
9
+ Suggested contents:
10
+
11
+ - `models/` optional model artifacts you want persisted in the bucket
12
+ - `cache-seed/` optional Hugging Face cache seeds
13
+ - `metadata/` optional JSON or CSV files used by startup/runtime logic
14
+
15
+ Notes:
16
+
17
+ - This folder is optional. If it is empty or missing, deploy still works.
18
+ - Runtime model caching primarily uses `/data/.hf_cache` and `/data/.cache/huggingface` inside the mounted Space volume.
19
+ - Keep secrets out of this folder.
start.sh CHANGED
@@ -17,5 +17,21 @@ if ! [[ "${OMP_NUM_THREADS:-}" =~ ^[0-9]+$ ]]; then
17
  export OMP_NUM_THREADS=1
18
  fi
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  echo "Starting uvicorn on port $PORT"
21
  exec uvicorn app.main:app --host 0.0.0.0 --port "$PORT" --log-level info
 
17
  export OMP_NUM_THREADS=1
18
  fi
19
 
20
+ # Prefer persistent HF cache on Spaces when /data is available and writable.
21
+ PERSIST_ROOT="${HF_PERSISTENT_CACHE_ROOT:-/data}"
22
+ if [ "${HF_PERSISTENT_CACHE:-true}" = "true" ] && [ -d "$PERSIST_ROOT" ] && [ -w "$PERSIST_ROOT" ]; then
23
+ export HF_HOME="${HF_HOME:-$PERSIST_ROOT/.cache/huggingface}"
24
+ if [ -z "${HF_CACHE_DIR:-}" ] || [ "${HF_CACHE_DIR}" = ".hf_cache" ] || [ "${HF_CACHE_DIR}" = "/app/.hf_cache" ]; then
25
+ export HF_CACHE_DIR="$PERSIST_ROOT/.hf_cache"
26
+ fi
27
+ else
28
+ export HF_HOME="${HF_HOME:-/app/.cache/huggingface}"
29
+ export HF_CACHE_DIR="${HF_CACHE_DIR:-/app/.hf_cache}"
30
+ fi
31
+
32
+ mkdir -p "$HF_HOME" "$HF_CACHE_DIR" 2>/dev/null || true
33
+ echo "Using HF cache dir: $HF_CACHE_DIR"
34
+ echo "Using HF home dir: $HF_HOME"
35
+
36
  echo "Starting uvicorn on port $PORT"
37
  exec uvicorn app.main:app --host 0.0.0.0 --port "$PORT" --log-level info
sync-bucket.ps1 ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env pwsh
2
+
3
+ Set-StrictMode -Version Latest
4
+ $ErrorActionPreference = 'Stop'
5
+
6
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
7
+ Set-Location $ScriptDir
8
+
9
+ if (Test-Path .env) {
10
+ Get-Content .env | ForEach-Object {
11
+ $line = $_.Trim()
12
+ if ($line -eq '' -or $line.StartsWith('#')) { return }
13
+ $parts = $line.Split('=', 2)
14
+ if ($parts.Count -ne 2) { return }
15
+ $name = $parts[0].Trim()
16
+ $value = $parts[1].Trim()
17
+
18
+ if ($value.StartsWith('"') -and $value.EndsWith('"')) {
19
+ $value = $value.Substring(1, $value.Length - 2)
20
+ } elseif ($value.StartsWith("'") -and $value.EndsWith("'")) {
21
+ $value = $value.Substring(1, $value.Length - 2)
22
+ }
23
+
24
+ if ($name -match '^[A-Za-z_][A-Za-z0-9_]*$') {
25
+ [Environment]::SetEnvironmentVariable($name, $value, 'Process')
26
+ }
27
+ }
28
+ }
29
+
30
+ $BucketUri = if ($env:HF_BUCKET_URI) { $env:HF_BUCKET_URI } else { 'hf://buckets/lukhsaankumar/DeepFakeDetectorBackend-storage' }
31
+ $LocalDir = if ($args.Count -gt 0 -and $args[0]) { $args[0] } elseif ($env:HF_BUCKET_LOCAL_DIR) { $env:HF_BUCKET_LOCAL_DIR } else { './data' }
32
+ $DeleteFlag = if ($env:HF_BUCKET_DELETE) { $env:HF_BUCKET_DELETE } else { 'false' }
33
+
34
+ if (-not (Get-Command hf -ErrorAction SilentlyContinue)) {
35
+ Write-Error 'Hugging Face CLI (hf) is not installed. Install guide: https://hf.co/docs/huggingface_hub/guides/cli'
36
+ }
37
+
38
+ try {
39
+ hf auth whoami | Out-Null
40
+ } catch {
41
+ Write-Error 'Hugging Face CLI is not authenticated. Run: hf auth login'
42
+ }
43
+
44
+ if (-not (Test-Path $LocalDir -PathType Container)) {
45
+ Write-Error "Local directory does not exist: $LocalDir"
46
+ }
47
+
48
+ Write-Host 'Syncing local directory to HF bucket'
49
+ Write-Host " Local : $LocalDir"
50
+ Write-Host " Bucket: $BucketUri"
51
+
52
+ if ($DeleteFlag -eq 'true') {
53
+ Write-Host ' Mode : mirror (delete remote files not present locally)'
54
+ hf sync $LocalDir $BucketUri --delete
55
+ } else {
56
+ Write-Host ' Mode : additive (no remote deletes)'
57
+ hf sync $LocalDir $BucketUri
58
+ }
59
+
60
+ Write-Host 'Bucket sync completed successfully.'
sync-bucket.sh ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ # Sync local data into a Hugging Face bucket.
4
+ # Defaults are loaded from backend/.env when present.
5
+
6
+ set -euo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ cd "$SCRIPT_DIR"
10
+
11
+ if [ -f ".env" ]; then
12
+ while IFS= read -r raw_line || [ -n "$raw_line" ]; do
13
+ line="${raw_line%$'\r'}"
14
+ case "$line" in
15
+ ''|'#'*) continue ;;
16
+ esac
17
+
18
+ if [[ "$line" == *=* ]]; then
19
+ key="${line%%=*}"
20
+ value="${line#*=}"
21
+ key="$(echo "$key" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
22
+
23
+ if [[ ! "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
24
+ continue
25
+ fi
26
+
27
+ if [[ "$value" =~ ^\".*\"$ ]]; then
28
+ value="${value:1:${#value}-2}"
29
+ elif [[ "$value" =~ ^\'.*\'$ ]]; then
30
+ value="${value:1:${#value}-2}"
31
+ else
32
+ value="$(echo "$value" | sed 's/[[:space:]]#.*$//;s/[[:space:]]*$//')"
33
+ fi
34
+
35
+ export "$key=$value"
36
+ fi
37
+ done < ./.env
38
+ fi
39
+
40
+ BUCKET_URI="${HF_BUCKET_URI:-hf://buckets/<username>/<bucket-name>}"
41
+ LOCAL_DIR="${1:-${HF_BUCKET_LOCAL_DIR:-./data}}"
42
+ DELETE_FLAG="${HF_BUCKET_DELETE:-false}"
43
+
44
+ # Force host-safe HF cache path for CLI operations.
45
+ HF_HOME_HOST_DEFAULT="${HOME:-$PWD}/.cache/huggingface"
46
+ HF_HOME="${HF_HOME_HOST:-${DEPLOY_HF_HOME:-$HF_HOME_HOST_DEFAULT}}"
47
+ export HF_HOME
48
+
49
+ if [[ "$BUCKET_URI" == *"<username>"* ]] || [[ "$BUCKET_URI" == *"<bucket-name>"* ]]; then
50
+ echo "ERROR: HF_BUCKET_URI is still a placeholder: $BUCKET_URI"
51
+ echo "Set HF_BUCKET_URI in backend/.env to your real bucket URI."
52
+ exit 1
53
+ fi
54
+
55
+ if ! command -v hf >/dev/null 2>&1; then
56
+ echo "ERROR: Hugging Face CLI (hf) is not installed."
57
+ echo "Install guide: https://hf.co/docs/huggingface_hub/guides/cli"
58
+ exit 1
59
+ fi
60
+
61
+ if ! hf auth whoami >/dev/null 2>&1; then
62
+ echo "ERROR: Hugging Face CLI is not authenticated. Run: hf auth login"
63
+ exit 1
64
+ fi
65
+
66
+ if [ ! -d "$LOCAL_DIR" ]; then
67
+ echo "ERROR: Local directory does not exist: $LOCAL_DIR"
68
+ exit 1
69
+ fi
70
+
71
+ echo "Syncing local directory to HF bucket"
72
+ echo " Local : $LOCAL_DIR"
73
+ echo " Bucket: $BUCKET_URI"
74
+
75
+ if [ "$DELETE_FLAG" = "true" ]; then
76
+ echo " Mode : mirror (delete remote files not present locally)"
77
+ hf sync "$LOCAL_DIR" "$BUCKET_URI" --delete
78
+ else
79
+ echo " Mode : additive (no remote deletes)"
80
+ hf sync "$LOCAL_DIR" "$BUCKET_URI"
81
+ fi
82
+
83
+ echo "Bucket sync completed successfully."