Skip to content

Feat/model service split - #2390

Closed
ahmedali-80 wants to merge 1389 commits into
fastapi:masterfrom
ahmedali-80:feat/model-service-split
Closed

Feat/model service split#2390
ahmedali-80 wants to merge 1389 commits into
fastapi:masterfrom
ahmedali-80:feat/model-service-split

Conversation

@ahmedali-80

@ahmedali-80 ahmedali-80 commented Jul 28, 2026

Copy link
Copy Markdown

Pull Request

Discussion:

Description

AI Disclaimer

AI transcript

Checklist

  • This PR links to a GitHub Discussion for the proposed code change.
  • I added tests for the change.
  • The new or updated tests fail on the main branch and pass on this PR.
  • Coverage stays at 100%.
  • The documentation explains the change if needed.

github-actions Bot and others added 30 commits January 20, 2026 20:48
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.556.0 to 0.562.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.562.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 0.562.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
)

Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
…update old passwords using Bcrypt (fastapi#2104)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…tacks (fastapi#2105)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Bumps traefik from 3.0 to 3.6.

---
updated-dependencies:
- dependency-name: traefik
  dependency-version: '3.6'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
github-actions Bot and others added 27 commits July 4, 2026 18:55
…tapi#2333)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
Co-authored-by: Yurii Motov <109919500+YuriiMotov@users.noreply.github.com>
Co-authored-by: Alejandra <alejsdev@gmail.com>
…stapi#2379)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…3.0,<3.0.0 (fastapi#2373)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…astapi#2380)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
Every torch-backed model now runs in its own container under services/ and is
reached over HTTP. The backend keeps its API surface, swagger, auth, faiss and
TMX generation, and loads no weights at all.

  labse    LaBSE embeddings + similarity
  qe       CometKiwi (reference-free QE)
  comet    wmt22-comet-da (reference-based)  -- same image as qe
  metricx  MetricX-24 hybrid
  langid   lingua restricted to en+fr        -- replaces fastText

qe and comet share one image: their dependency trees resolve identically, so
separate images would duplicate rather than isolate.

  backend image        7.39 GB -> 1.57 GB
  backend lock         145 -> 101 packages
  startup to healthy   ~2 min -> ~2 s
  build context        11 GB -> nil (aimodels now ignored)

Parity verified against the in-process models before each cutover:

  CometKiwi        0.00e+00  bit-identical
  wmt22-comet-da   0.00e+00  bit-identical
  LaBSE            4.38e-05  the backend's own 4dp rounding
  MetricX          5.86e-03  ~3 bf16 ulps on a 0-25 scale

Stopping any one service returns 503 + Retry-After on only its own route;
everything else keeps serving, and the route recovers unattended.

Dependency notes, each found by running it rather than reasoning about it:

- setuptools pinned <81 in comet-svc. unbabel-comet pins torchmetrics 0.10.3,
  which imports pkg_resources at module scope; setuptools 81 removed it. The
  backend only ever survived this because its lock froze setuptools at 80.7.1.
- transformers pinned ==4.51.3 in metricx-svc. 5.x drops a symbol
  MT5ForRegression needs; 4.57.6 breaks the MT5 attention mask. labse-svc runs
  5.14.1 quite happily -- which is the whole argument for per-service locks.
- metricx-svc installs gcc: torch's triton backend JIT-compiles its CUDA utils
  at runtime and the slim base ships no compiler.
- sacrebleu declared explicitly; it had been arriving via unbabel-comet.
- nptyping kept despite looking unused: hlepor imports it without declaring it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings 7 months of template changes: 5 security fixes, ~70 dependency bumps,
the uv workspace restructure, the models refactor and Python 3.14.

Structural changes taken from upstream:

- uv workspace: root pyproject.toml + root uv.lock; backend/uv.lock removed and
  the Docker build context moved to the repo root.
- Python 3.10 -> 3.14. numpy 1.26.4 -> 2.5.1, faiss-cpu -> 1.14.3.
- passlib/bcrypt -> pwdlib[argon2,bcrypt].
- docker-compose*.yml -> compose*.yml (git tracked these as renames, so the
  five model services carried over intact).
- postgres 17 -> 18. The local data volume was initialised by 17 and PG18
  refuses to start on it, so app-db-data was dropped and recreated. Other
  environments still need a real pg_upgrade or dump/restore.

Local decisions:

- Took upstream's models.py and dropped the local app/models/ package. The
  class sets were identical, so nothing was lost, and upstream's version adds
  created_at/get_datetime_utc. The 7 `from app.models.users import` lines are
  back to `from app.models import`.
- Vendored hlepor into app/models_ml/hlepor_vendored.py. The published package
  imports nptyping, which is unmaintained and hard-caps numpy <2 -- which
  blocked Python 3.14 for the whole backend. nptyping's NDArray was only ever
  used in annotations, and PEP 563 meant they were never evaluated, so the
  import was removed and the annotations now say np.ndarray. hlepor, lepor and
  nptyping are gone from the dependency list. Verified the metric still
  discriminates: 1.000 identical, 0.811 near, 0.310 unrelated.

Two things the merge would otherwise have broken silently:

- sacre_bleu.py loaded libED.so from the hardcoded /app/app/lib/. The workspace
  layout moves the app to /app/backend/app, so characTER would have failed at
  import. Now resolved relative to __file__.
- No root .dockerignore existed. With the build context at the repo root, the
  ~11 GB of model weights under backend/aimodels would ship to the daemon on
  every build. Added.

Verified after merge: 60/60 backend tests pass, all 9 metric routes 200,
/memory/align and /memory/align-uni 15/15 correct, auth round trip works on
pwdlib, and /users/me returns the new created_at field.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
labse-svc, metricx-svc and langid-svc move from python:3.11-slim-bookworm to
python:3.14-slim-trixie. comet-svc moves to python:3.12-slim-trixie.

comet-svc cannot reach 3.14: unbabel-comet pins numpy<2.0.0, and the last
numpy 1.x (1.26.4) publishes no wheel past cp312. On 3.13+ the resolver falls
back to the sdist and the meson build fails looking for a C compiler; adding
one would not help, since numpy 1.26 does not build against the 3.14 C API.
The cap is declared in both the Dockerfile and requires-python so `uv lock`
cannot resolve something the image could never install.

This is the split doing its job. Under one shared lock, unbabel-comet would
hold every service — and the backend — at numpy 1.x and Python 3.12.

Also:
- uv 0.7.5 -> 0.9.26 in all four Dockerfiles. 0.7.5 predates 3.14 and does not
  resolve cp314 wheels.
- Locks regenerated; ~5000 lines lighter, as the raised requires-python floor
  drops the 3.10-3.13 resolution forks.
- lingua 2.1.1 -> 2.2.0 (2.1.1 stops at cp313, so 3.14 forces it). Its bundled
  n-gram data grew to 292 MB, taking langid-svc from 338 MB to 550 MB. Disk
  only — it still preloads just en and fr. Documented with the way back.
- Base image CVEs: 1 critical + 4 high -> 1 critical + 2 high. The remaining
  critical is unfixed upstream in Debian.

Verified on the rebuilt images: all five services reach /ready inside 40 s and
report the intended interpreter (labse/metricx/langid 3.14.6 + numpy 2.5.1,
qe/comet 3.12.13 + numpy 1.26.4). All 13 backend routes return 200 with
plausible scores, and alignment recovers 12/12 pairs from a shuffled EN/FR
corpus. metricx exercises the CUDA forward pass, so triton still JIT-compiles
on 3.14. The CPU torch variant also builds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
/v1/embed had exactly two callers, both inside align_sentences. The backend
pulled every embedding over HTTP — ~4 KB per sentence after base64, so 820 MB
on a 100k+100k corpus — purely to hand them to a local faiss index, then threw
them away. It never kept a vector for anything else.

The vectors are produced in GPU memory a few lines from where the kNN runs, so
the search now lives there too: new POST /v1/align takes text and returns
{src_idx, trg_idx, score} triples. Indices, not text — the caller already holds
the strings, and echoing them would re-inflate the payload this exists to
shrink.

Moved into services/labse/app/aligner.py: normalization, bidirectional faiss
kNN, ratio-margin scoring, greedy one-to-one selection. Left in the backend:
the 10-200 char filter, langid routing for align-uni, TMX, response shaping.

Backend drops faiss-cpu (1.66 GB -> 1.59 GB, lock 101 -> 99 packages). numpy
stays, but only for the vendored hLEPOR, which is array maths throughout —
nothing else in the app touches it now. labse-svc grows 5.51 -> 5.58 GB.

Two deliberate changes to the moved code:

- score_candidates was a Python double loop doing one 768-dim dot per
  iteration; it is now a gather plus einsum. Verified against a verbatim
  transcription of the original inside the built image: max |delta| 6.0e-07
  (float32 reassociation) and *identical* selected pairs at n=30, 200 and 2000
  random unit vectors, which tie far more often than real bitext.

- k is clamped to the corpus size. faiss pads with -1 when k exceeds the index
  and a -1 index silently wraps to the last row, so a corpus smaller than k
  scored against garbage and fell below threshold. /memory/align on two
  sentences returned nothing; it now returns the correct pairs. This is the
  only behavioural difference, and only for corpora smaller than k.

/v1/embed stays on the service. It has no caller here now, but the raw
primitive is the reusable one and the next consumer may not want alignment.

The search stays on CPU via faiss-cpu even though the vectors are in VRAM: an
index sized for a large corpus would compete with the model for GPU memory, and
that trade is unmeasured. Moving it to the GPU is a separate decision.

Verified: alignment output byte-identical to the pre-move baseline on a
shuffled 30-pair EN/FR corpus, via both /memory/align and /memory/align-uni,
30/30 correct each; metrics/labse unchanged; all 13 routes 200; 60/60 backend
tests pass; faiss confirmed absent from the backend image.

Also: metricx.py now uses the mean the client already computes, which returns
0.0 for an empty batch where np.mean produced a NaN that is not valid JSON.
Two lint errors surfaced by the py314 ruff target fixed (UP037, B905).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
align_sentences_special constructed OpenAI(api_key="sk-proj-...") from a literal
committed in 4d4d3e1. It now reads settings.OPENAI_API_KEY and raises if unset,
so a missing key fails at the call site instead of a secret living in the tree.

.env carries an empty placeholder. That file is committed, so the comment says
plainly not to put a real key in it.

The literal is purged from history in the following step -- this branch has
never been pushed, so the rewrite costs nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

This PR modifies dependency files (pyproject.toml or uv.lock), which is restricted to members of the fastapi organization on GitHub.

If you need a dependency change, please open a discussion describing what you need and why.

Closing this PR automatically.

@github-actions github-actions Bot closed this Jul 28, 2026
@ahmedali-80
ahmedali-80 deleted the feat/model-service-split branch July 28, 2026 13:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.