Skip to content

docs(mlx-deploy): fix setup steps that prevent a working install - #242

Open
thijs-hakkenberg wants to merge 2 commits into
zai-org:mainfrom
thijs-hakkenberg:docs/fix-mlx-deploy-readme
Open

thijs-hakkenberg wants to merge 2 commits into
zai-org:mainfrom
thijs-hakkenberg:docs/fix-mlx-deploy-readme

Conversation

@thijs-hakkenberg

Copy link
Copy Markdown

While testing examples/mlx-deploy/README.md end-to-end on an Apple M1 Pro (macOS 26.6, 32 GB), I hit three issues that stop the documented flow from working, plus two claims that have gone stale. All fixes are verified by re-running the corrected steps.

Blocking issues

1. Step 3 omits the layout extra. The guide says pip install -e ., which does not install torch, torchvision or transformers. The pipeline then fails at startup:

ImportError: Layout detector dependencies are missing or incompatible.
Try: pip install 'glmocr[layout]'.

Changed to pip install -e '.[layout]'.

2. Step 4's config snippet cannot work as shown. It presents a four-key ocr_api block as a usable custom config file. But a --config file replaces the YAML baseline rather than merging with the packaged glmocr/config.yaml — in GlmOcrConfig.from_env, the custom path becomes the sole YAML layer, with only GLMOCR_* env vars and keyword overrides applied on top. Omitted keys fall back to built-in dataclass defaults, not to the packaged values.

So that snippet silently produces:

  • pipeline.layout.model_dir → None (no built-in default), failing with pipeline.layout.model_dir is required for self-hosted layout
  • layout.id2label → {}
  • layout.label_task_mapping → {}
  • page_loader.task_prompt_mapping → None

The last three are needed to label and route detected regions. The section now documents the merge semantics and gives two approaches that actually work (edit the packaged config in place, or copy it and edit the copy).

3. Two model: keys are easy to confuse. config.yaml has one under pipeline.maas and one under pipeline.ocr_api. Editing the wrong one leaves ocr_api.model as glm-ocr, and the mlx-vlm server rejects it with a bare API server returned status code: 400. I hit this myself while testing. Added a callout and a troubleshooting entry.

Stale claims

4. mlx-vlm no longer needs a git install. The guide says glm_ocr "is not yet available in the latest PyPI release 0.3.10". PyPI now has stable 0.7.0, and I confirmed its wheel contains the glm_ocr model files. Switched to pip install 'mlx-vlm>=0.7.0', keeping the git install as a documented fallback.

5. The "Why a Separate Environment?" rationale has converged. It describes a conflict between mlx-vlm's transformers>=5.0.0rc3 and the SDK's pin. The SDK's layout extra requires transformers>=5.3.0, so the ranges now overlap and both envs resolved to transformers 5.16.1 here. I kept the two-environment setup (it is what the guide is tested against and keeps server and SDK independently upgradable) but corrected the stated reason.

Verification

With the corrected steps, against mlx-community/GLM-OCR-bf16 on Metal:

  • mlx_vlm.server health check — OK (~110 tok/s, 2.3 GB peak)
  • CLI glmocr parse on examples/source/code.png, seal.png, table.png — OK
  • Python API parse(...) + result.save(...) — OK
  • A real single-page scanned invoice PDF — 21 regions, 11.6 s; every figure matches the PDF text layer exactly
  • Both documented config approaches (Option A and Option B) — OK

Docs-only change; no code touched.

🤖 Generated with Claude Code

https://claude.ai/code/session_017e4v9dzir569nZhfSPydRE

Testing the MLX guide end-to-end on an M1 Pro surfaced three issues that
stop the documented flow from working, plus two stale claims.

- Step 3 installed the SDK with a bare `pip install -e .`, which omits
  torch/torchvision/transformers. The pipeline then fails at startup with
  `ImportError: Layout detector dependencies are missing or incompatible`.
  Use `pip install -e '.[layout]'`.

- Step 4 presented a four-key `ocr_api` snippet as a usable custom config.
  A `--config` file replaces the YAML baseline rather than merging with the
  packaged `glmocr/config.yaml` (see `GlmOcrConfig.from_env`), so that
  snippet drops `layout.model_dir` and the run dies with
  `pipeline.layout.model_dir is required for self-hosted layout`. It also
  empties `layout.id2label`, `layout.label_task_mapping` and
  `page_loader.task_prompt_mapping`. Document the merge semantics and give
  two working approaches instead.

- Note that `config.yaml` has two `model:` keys (`maas` and `ocr_api`);
  editing the wrong one leaves `ocr_api.model` as `glm-ocr` and the server
  returns an opaque HTTP 400.

- mlx-vlm ships `glm_ocr` in PyPI 0.7.0, so the git install is no longer
  required (kept as a fallback).

- The transformers conflict behind "Why a Separate Environment?" has
  converged: mlx-vlm needs >=5.0.0rc3 and the SDK's layout extra needs
  >=5.3.0. Two environments remain what this guide is tested against.

Verified by running the corrected steps end-to-end against
mlx-community/GLM-OCR-bf16 on Metal: CLI, Python API, and a scanned
invoice PDF all parse successfully.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017e4v9dzir569nZhfSPydRE
The SDK runs up to pipeline.max_workers region requests in parallel (32 in
the packaged config). The mlx-vlm server does not handle concurrent vision
requests safely: generations are silently truncated and corrupted, dropping
table rows and stopping mid-number, with nothing logged.

Measured over a 194-file / 228-page invoice corpus on an M1 Pro against
mlx-community/GLM-OCR-bf16:

- max_workers=4: 91.3% of invoice totals recovered, not reproducible
- max_workers=1: ~96.6%, reproducible

Concurrency also breaks determinism despite temperature=0.0 / top_k=1. Of 10
byte-identical duplicate PDFs in that corpus, 7 produced different text at
max_workers=4 (3 substantially divergent). Re-parsing one file four times
diverged on the fourth run; at max_workers=1 three consecutive runs were
byte-identical.

The mlx-vlm server serves requests sequentially regardless, so max_workers=1
costs little throughput.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017e4v9dzir569nZhfSPydRE
@thijs-hakkenberg

Copy link
Copy Markdown
Author

Added a fourth issue found while stress-testing this guide on a 194-file / 228-page invoice corpus (Apple M1 Pro, mlx-community/GLM-OCR-bf16).

pipeline.max_workers must be 1 for mlx-vlm. The packaged config defaults to 32. The mlx-vlm server does not handle concurrent vision requests safely — under concurrency, generations are silently truncated and corrupted: output stops mid-number, repeats the wrong label, or drops a table row entirely. Nothing is logged, so it presents as "the model missed a field" rather than as an error.

Same file, same config, only max_workers differs:

  • max_workers: 1 → <td>Importe Total: USD</td><td>1.512,50</td> ✅
  • max_workers: 4 → repeats Importe Exento and cuts off at 262,5; the Total row is gone ❌

Measured impact on recovering the invoice total (ground truth from filenames, n=149):

max_workers Correct Reproducible
4 91.3% no
1 ~96.6% yes

It also breaks determinism despite temperature: 0.0 / top_k: 1. The corpus happened to contain 10 byte-identical duplicate PDFs (same MD5): at max_workers: 4, 7 of 10 produced different text, 3 substantially (0.64–0.88 similarity). Re-parsing a single file 4x diverged on the 4th run; at max_workers: 1, three consecutive runs were byte-identical.

Since the server serializes requests anyway, max_workers: 1 costs little throughput and removes the corruption.

I have not filed this upstream with mlx-vlm — happy to, if you agree the fix belongs there rather than in this guide. Documenting it here seems worthwhile either way, since the SDK's default actively triggers it.

This branch has not been deployed

No deployments
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.

2 participants