Ship prebuilt embeddings; stop embedding at Docker build time (fixes build timeout)
Browse filesThe Dockerfile ran 'python -m canlex.embed' at image build, embedding the whole
corpus on the build host. As the corpus grew to ~20k chunks that step reached
~33 minutes and began tripping Hugging Face Spaces' job timeout -- the build
finished and pushed the image, but the job was marked failed and the container
torn down, taking the server down.
Fix: commit data/processed/embeddings.npz as a git-LFS artifact (it is
deterministic and ~34 MB) and COPY it into the image; the build now only
pre-fetches the bge query model and the reranker (a few minutes) instead of
re-embedding. embeddings.npz un-gitignored, LFS-tracked, and removed from
.dockerignore. Verified the committed vectors' ids match the committed corpus
exactly (20,381).
Workflow change: after any corpus edit, run 'python -m canlex.embed' and commit
the regenerated embeddings.npz (it no longer rebuilds itself in the image).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- .dockerignore +1 -4
- .gitattributes +1 -0
- .gitignore +0 -1
- Dockerfile +12 -4
- data/processed/embeddings.npz +3 -0
|
@@ -6,12 +6,9 @@ __pycache__/
|
|
| 6 |
**/__pycache__/
|
| 7 |
*.pyc
|
| 8 |
|
| 9 |
-
# Raw downloads are not needed -- only data/processed/
|
| 10 |
data/raw/
|
| 11 |
|
| 12 |
-
# Regenerated inside the image by 'python -m canlex.embed'.
|
| 13 |
-
data/processed/embeddings.npz
|
| 14 |
-
|
| 15 |
# Local-only: the CanLII key (injected as a runtime secret) and disk caches.
|
| 16 |
canlii_key.txt
|
| 17 |
data/citator_dbmap.json
|
|
|
|
| 6 |
**/__pycache__/
|
| 7 |
*.pyc
|
| 8 |
|
| 9 |
+
# Raw downloads are not needed -- only data/processed/ is.
|
| 10 |
data/raw/
|
| 11 |
|
|
|
|
|
|
|
|
|
|
| 12 |
# Local-only: the CanLII key (injected as a runtime secret) and disk caches.
|
| 13 |
canlii_key.txt
|
| 14 |
data/citator_dbmap.json
|
|
@@ -1 +1,2 @@
|
|
| 1 |
data/processed/caselaw.json filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 1 |
data/processed/caselaw.json filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
data/processed/embeddings.npz filter=lfs diff=lfs merge=lfs -text
|
|
@@ -3,6 +3,5 @@ __pycache__/
|
|
| 3 |
.venv/
|
| 4 |
venv/
|
| 5 |
data/raw/
|
| 6 |
-
data/processed/embeddings.npz
|
| 7 |
canlii_key.txt
|
| 8 |
data/citator_*.json
|
|
|
|
| 3 |
.venv/
|
| 4 |
venv/
|
| 5 |
data/raw/
|
|
|
|
| 6 |
canlii_key.txt
|
| 7 |
data/citator_*.json
|
|
@@ -19,9 +19,15 @@ WORKDIR /app
|
|
| 19 |
COPY requirements.txt .
|
| 20 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 21 |
|
| 22 |
-
# Application code
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
COPY --chown=app:app canlex/ ./canlex/
|
| 24 |
COPY --chown=app:app data/processed/*.json ./data/processed/
|
|
|
|
| 25 |
|
| 26 |
USER app
|
| 27 |
ENV HOME=/app \
|
|
@@ -30,9 +36,11 @@ ENV HOME=/app \
|
|
| 30 |
PORT=7860 \
|
| 31 |
PYTHONUNBUFFERED=1
|
| 32 |
|
| 33 |
-
#
|
| 34 |
-
#
|
| 35 |
-
|
|
|
|
|
|
|
| 36 |
&& python -c "from canlex.rerank import Reranker; Reranker()"
|
| 37 |
|
| 38 |
# From here on, model files are served from the baked cache, never fetched.
|
|
|
|
| 19 |
COPY requirements.txt .
|
| 20 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 21 |
|
| 22 |
+
# Application code, the processed corpus (section-chunk JSON), and the prebuilt
|
| 23 |
+
# semantic embeddings (embeddings.npz, committed via git-LFS). Shipping the
|
| 24 |
+
# embeddings as an artifact keeps the build fast: embedding ~20k chunks on the
|
| 25 |
+
# build host took ~33 min and was tripping Hugging Face's job timeout as the
|
| 26 |
+
# corpus grew. Regenerate with 'python -m canlex.embed' after any corpus change
|
| 27 |
+
# and commit the result.
|
| 28 |
COPY --chown=app:app canlex/ ./canlex/
|
| 29 |
COPY --chown=app:app data/processed/*.json ./data/processed/
|
| 30 |
+
COPY --chown=app:app data/processed/embeddings.npz ./data/processed/
|
| 31 |
|
| 32 |
USER app
|
| 33 |
ENV HOME=/app \
|
|
|
|
| 36 |
PORT=7860 \
|
| 37 |
PYTHONUNBUFFERED=1
|
| 38 |
|
| 39 |
+
# Pre-fetch the embedder and cross-encoder models so the cache is baked into the
|
| 40 |
+
# image and the first request needs no network. The corpus is NOT re-embedded
|
| 41 |
+
# here -- the committed embeddings.npz is used as-is; only the bge query model
|
| 42 |
+
# (for embedding live queries) and the reranker are downloaded.
|
| 43 |
+
RUN python -c "from canlex.embed import Embedder; Embedder()" \
|
| 44 |
&& python -c "from canlex.rerank import Reranker; Reranker()"
|
| 45 |
|
| 46 |
# From here on, model files are served from the baked cache, never fetched.
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c1e6554d83a8a3a37d77a0f0e032e311ceb3187d2bb742170c05549e2c4d65f7
|
| 3 |
+
size 36126270
|