Publish Unlimited-OCR RDNA 4 runtime v0.1.0
Browse files- .gitattributes +2 -0
- .github/workflows/ci.yml +27 -0
- .gitignore +14 -0
- CHANGELOG.md +12 -0
- CITATION.cff +50 -0
- CONTRIBUTING.md +25 -0
- LICENSE +21 -0
- README.md +227 -0
- SECURITY.md +32 -0
- THIRD_PARTY_NOTICES.md +42 -0
- dist/unlimited_ocr_rdna4-0.1.0-py3-none-any.whl +0 -0
- dist/unlimited_ocr_rdna4-0.1.0.tar.gz +3 -0
- docs/PUBLISHING.md +37 -0
- docs/VALIDATION.md +87 -0
- docs/validation-gfx1201.json +130 -0
- huggingface/README.md +25 -0
- pyproject.toml +72 -0
- requirements/bootstrap.in +26 -0
- requirements/bootstrap.lock +1061 -0
- scripts/bootstrap-rocm.sh +203 -0
- scripts/check-validation.py +152 -0
- scripts/make-smoke-fixture.py +97 -0
- scripts/validate-smoke.sh +108 -0
- src/unlimited_ocr_rdna4/__init__.py +6 -0
- src/unlimited_ocr_rdna4/__main__.py +4 -0
- src/unlimited_ocr_rdna4/cli.py +228 -0
- src/unlimited_ocr_rdna4/constants.py +55 -0
- src/unlimited_ocr_rdna4/errors.py +17 -0
- src/unlimited_ocr_rdna4/infer.py +288 -0
- src/unlimited_ocr_rdna4/model_store.py +310 -0
- src/unlimited_ocr_rdna4/paths.py +27 -0
- src/unlimited_ocr_rdna4/pdf.py +128 -0
- src/unlimited_ocr_rdna4/postprocess.py +52 -0
- src/unlimited_ocr_rdna4/runtime.py +142 -0
- tests/fixtures/rdna4-smoke.pdf +3 -0
- tests/fixtures/rdna4-smoke.png +3 -0
- tests/test_bootstrap.py +17 -0
- tests/test_cli.py +33 -0
- tests/test_infer_output.py +40 -0
- tests/test_model_store.py +110 -0
- tests/test_paths.py +17 -0
- tests/test_pdf.py +52 -0
- tests/test_postprocess.py +36 -0
- tests/test_runtime.py +43 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
tests/fixtures/rdna4-smoke.pdf filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
tests/fixtures/rdna4-smoke.png filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/ci.yml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: CI
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
pull_request:
|
| 6 |
+
|
| 7 |
+
permissions:
|
| 8 |
+
contents: read
|
| 9 |
+
|
| 10 |
+
jobs:
|
| 11 |
+
test:
|
| 12 |
+
runs-on: ubuntu-latest
|
| 13 |
+
steps:
|
| 14 |
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
| 15 |
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
| 16 |
+
with:
|
| 17 |
+
python-version: "3.12"
|
| 18 |
+
cache: pip
|
| 19 |
+
- run: python -m pip install -e '.[dev]'
|
| 20 |
+
- run: ruff check .
|
| 21 |
+
- run: ruff format --check .
|
| 22 |
+
- run: pytest
|
| 23 |
+
- run: bash -n scripts/bootstrap-rocm.sh scripts/validate-smoke.sh
|
| 24 |
+
- run: python scripts/check-validation.py --help
|
| 25 |
+
- run: scripts/bootstrap-rocm.sh --dry-run
|
| 26 |
+
- run: python -m build
|
| 27 |
+
- run: twine check dist/*
|
.gitignore
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.venv/
|
| 2 |
+
.venv-*/
|
| 3 |
+
.pytest_cache/
|
| 4 |
+
.ruff_cache/
|
| 5 |
+
__pycache__/
|
| 6 |
+
*.py[cod]
|
| 7 |
+
build/
|
| 8 |
+
dist/
|
| 9 |
+
*.egg-info/
|
| 10 |
+
.coverage
|
| 11 |
+
htmlcov/
|
| 12 |
+
*.ocr.md
|
| 13 |
+
*.partial/
|
| 14 |
+
models/
|
CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Changelog
|
| 2 |
+
|
| 3 |
+
All notable changes to this project will be documented here.
|
| 4 |
+
|
| 5 |
+
## Unreleased
|
| 6 |
+
|
| 7 |
+
- add a checksum-pinned ROCm 7.2.1 bootstrap for Python 3.12;
|
| 8 |
+
- prepare Baidu Unlimited-OCR revision `07dea832e22aefee32ad281d4b80551282e1c168` without redistributing weights;
|
| 9 |
+
- add audited remote-code hardening and device-compatibility transforms;
|
| 10 |
+
- add single-RDNA-4 GPU diagnostics and stable ROCr UUID selection;
|
| 11 |
+
- support atomic Markdown output from images and page-scoped PDFs;
|
| 12 |
+
- add deterministic `gfx1201` image/PDF acceptance fixtures and validation evidence.
|
CITATION.cff
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
cff-version: 1.2.0
|
| 2 |
+
message: "If you use this runtime, cite both this project and the original Unlimited-OCR paper."
|
| 3 |
+
title: "Unlimited-OCR RDNA 4 Runtime"
|
| 4 |
+
type: software
|
| 5 |
+
authors:
|
| 6 |
+
- family-names: "von Kohorn"
|
| 7 |
+
given-names: "Douglas"
|
| 8 |
+
version: 0.1.0
|
| 9 |
+
url: "https://huggingface.co/dougvk/Unlimited-OCR-RDNA4"
|
| 10 |
+
repository-code: "https://huggingface.co/dougvk/Unlimited-OCR-RDNA4"
|
| 11 |
+
license: MIT
|
| 12 |
+
references:
|
| 13 |
+
- type: article
|
| 14 |
+
title: "Unlimited OCR Works"
|
| 15 |
+
authors:
|
| 16 |
+
- family-names: "Yin"
|
| 17 |
+
given-names: "Youyang"
|
| 18 |
+
- family-names: "Liu"
|
| 19 |
+
given-names: "Huanhuan"
|
| 20 |
+
- name: "YY"
|
| 21 |
+
- family-names: "Xie"
|
| 22 |
+
given-names: "Qunyi"
|
| 23 |
+
- family-names: "Liu"
|
| 24 |
+
given-names: "Chaorun"
|
| 25 |
+
- family-names: "Yang"
|
| 26 |
+
given-names: "Shiqi"
|
| 27 |
+
- family-names: "Wang"
|
| 28 |
+
given-names: "Shaohua"
|
| 29 |
+
- family-names: "Liu"
|
| 30 |
+
given-names: "Zhanlong"
|
| 31 |
+
- family-names: "Zou"
|
| 32 |
+
given-names: "Hao"
|
| 33 |
+
- family-names: "Chen"
|
| 34 |
+
given-names: "Jinyue"
|
| 35 |
+
- family-names: "Wei"
|
| 36 |
+
given-names: "Shu"
|
| 37 |
+
- family-names: "Wu"
|
| 38 |
+
given-names: "Jingjing"
|
| 39 |
+
- family-names: "Huang"
|
| 40 |
+
given-names: "Mingxin"
|
| 41 |
+
- family-names: "Wu"
|
| 42 |
+
given-names: "Zhen"
|
| 43 |
+
- family-names: "Wang"
|
| 44 |
+
given-names: "Guibin"
|
| 45 |
+
- family-names: "Du"
|
| 46 |
+
given-names: "Tengyu"
|
| 47 |
+
- family-names: "Jia"
|
| 48 |
+
given-names: "Lei"
|
| 49 |
+
year: 2026
|
| 50 |
+
url: "https://arxiv.org/abs/2606.23050"
|
CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributing
|
| 2 |
+
|
| 3 |
+
Contributions are welcome, especially independently reproduced RDNA 4 results.
|
| 4 |
+
|
| 5 |
+
Before opening a pull request:
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
ruff check .
|
| 9 |
+
ruff format --check .
|
| 10 |
+
pytest
|
| 11 |
+
python -m build
|
| 12 |
+
twine check dist/*
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
GPU-dependent changes must include:
|
| 16 |
+
|
| 17 |
+
- GPU model and architecture;
|
| 18 |
+
- OS, kernel, host ROCm, PyTorch, torchvision, Triton, and Transformers versions;
|
| 19 |
+
- the exact model revision;
|
| 20 |
+
- the command used;
|
| 21 |
+
- peak allocated VRAM and wall time;
|
| 22 |
+
- a statement about output comparison or quality evidence;
|
| 23 |
+
- confirmation that unrelated GPUs and system Python were not modified.
|
| 24 |
+
|
| 25 |
+
Do not commit model weights, generated OCR from private documents, Hugging Face tokens, or raw home-directory paths.
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Douglas von Kohorn
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
base_model: baidu/Unlimited-OCR
|
| 3 |
+
pipeline_tag: image-text-to-text
|
| 4 |
+
license: mit
|
| 5 |
+
tags:
|
| 6 |
+
- rocm
|
| 7 |
+
- amd
|
| 8 |
+
- rdna4
|
| 9 |
+
- gfx1201
|
| 10 |
+
- ocr
|
| 11 |
+
- runtime
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
# Unlimited-OCR RDNA 4
|
| 15 |
+
|
| 16 |
+
Single-GPU [Baidu Unlimited-OCR](https://huggingface.co/baidu/Unlimited-OCR) inference for AMD RDNA 4
|
| 17 |
+
(`gfx1200` / `gfx1201`) using native ROCm PyTorch, verified end to end on `gfx1201`.
|
| 18 |
+
|
| 19 |
+
This project does **not** redistribute or rename Baidu's weights. It provides a reproducible runtime around the original
|
| 20 |
+
BF16 checkpoint: an isolated AMD wheel bootstrap, pinned model preparation, audited custom-code patching, single-GPU
|
| 21 |
+
selection, local-only model resolution during inference, atomic output, bounded PDF handling, diagnostics, and
|
| 22 |
+
validation evidence.
|
| 23 |
+
|
| 24 |
+
> Status: beta. Verified on 31 July 2026 with an AMD Radeon RX 9070 XT (`gfx1201`). This is not an AMD or Baidu project.
|
| 25 |
+
|
| 26 |
+
## Verified configuration
|
| 27 |
+
|
| 28 |
+
| Component | Verified value |
|
| 29 |
+
|---|---|
|
| 30 |
+
| GPU | AMD Radeon RX 9070 XT, 16 GB |
|
| 31 |
+
| GPU allocation | One isolated GPU |
|
| 32 |
+
| OS | Ubuntu 24.04.4 |
|
| 33 |
+
| Kernel | 6.17.0-40-generic |
|
| 34 |
+
| Host ROCm | 7.2.1 |
|
| 35 |
+
| PyTorch | 2.9.1 + ROCm 7.2.1 |
|
| 36 |
+
| Transformers | 4.57.1 |
|
| 37 |
+
| Matplotlib | 3.10.8 |
|
| 38 |
+
| Model revision | `07dea832e22aefee32ad281d4b80551282e1c168` |
|
| 39 |
+
| Weight precision | Original BF16; no quantization |
|
| 40 |
+
| Peak allocated VRAM | 8.25 GiB on the 1600×2000 validation page |
|
| 41 |
+
|
| 42 |
+
The initial validation recovered headings, reading order, a structured table, euro amounts, a formula, and an exact
|
| 43 |
+
checksum. Repeated greedy generation was byte-identical after deterministic tag cleanup. See
|
| 44 |
+
[docs/VALIDATION.md](docs/VALIDATION.md) for the evidence and its limits.
|
| 45 |
+
|
| 46 |
+
## Quick start
|
| 47 |
+
|
| 48 |
+
The bootstrap downloads checksum-pinned AMD ROCm wheels into your XDG cache, installs every other runtime/build
|
| 49 |
+
dependency from `requirements/bootstrap.lock` with hashes required, builds this checkout without build isolation, and
|
| 50 |
+
creates a repository-local virtual environment. It does not use `sudo`, alter `/opt/rocm`, or touch the system Python.
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
git clone https://huggingface.co/dougvk/Unlimited-OCR-RDNA4
|
| 54 |
+
cd Unlimited-OCR-RDNA4
|
| 55 |
+
|
| 56 |
+
./scripts/bootstrap-rocm.sh
|
| 57 |
+
.venv/bin/unlimited-ocr-rdna4 prepare
|
| 58 |
+
.venv/bin/unlimited-ocr-rdna4 run --input page.png
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
The model download is approximately 6.78 GB. The verified AMD wheel set is approximately 1.9 GB before installation.
|
| 62 |
+
|
| 63 |
+
PDFs are rendered and parsed page by page:
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
.venv/bin/unlimited-ocr-rdna4 run \
|
| 67 |
+
--input document.pdf \
|
| 68 |
+
--output document.md \
|
| 69 |
+
--max-pages 10 \
|
| 70 |
+
--dpi 200
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
## Commands
|
| 74 |
+
|
| 75 |
+
### `prepare`
|
| 76 |
+
|
| 77 |
+
Downloads the pinned original checkpoint, fully hashes the 6.67 GB safetensors file and every behavior-defining model,
|
| 78 |
+
configuration, tokenizer, and index file, preserves Baidu's original model source, and applies a narrowly scoped audited
|
| 79 |
+
patch.
|
| 80 |
+
|
| 81 |
+
```bash
|
| 82 |
+
unlimited-ocr-rdna4 prepare
|
| 83 |
+
unlimited-ocr-rdna4 prepare --dry-run --json
|
| 84 |
+
unlimited-ocr-rdna4 prepare --model-dir /data/models/unlimited-ocr
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
Preparation is serialized and idempotent. It uses a unique partial directory, validates the exact manifest and file set,
|
| 88 |
+
and refuses unknown, extra, symlinked, or mismatched files instead of executing them.
|
| 89 |
+
|
| 90 |
+
### `doctor`
|
| 91 |
+
|
| 92 |
+
Reports the active PyTorch/HIP stack, visible devices, RDNA 4 architecture, BF16 capability, and model readiness.
|
| 93 |
+
|
| 94 |
+
```bash
|
| 95 |
+
unlimited-ocr-rdna4 doctor
|
| 96 |
+
unlimited-ocr-rdna4 doctor --device 0 --require-model
|
| 97 |
+
unlimited-ocr-rdna4 doctor --json
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
### `run`
|
| 101 |
+
|
| 102 |
+
Parses one image or PDF and publishes untrusted model output atomically. The content may include Markdown and raw HTML;
|
| 103 |
+
do not render it in a privileged origin without sanitization.
|
| 104 |
+
|
| 105 |
+
```bash
|
| 106 |
+
unlimited-ocr-rdna4 run --input scan.png
|
| 107 |
+
unlimited-ocr-rdna4 run --input scan.png --output scan.md --device 0
|
| 108 |
+
unlimited-ocr-rdna4 run --input report.pdf --start-page 21 --max-pages 10
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
Important flags:
|
| 112 |
+
|
| 113 |
+
| Flag | Default | Meaning |
|
| 114 |
+
|---|---:|---|
|
| 115 |
+
| `--device` | `$UNLIMITED_OCR_DEVICE` or `0` | ROCm ordinal or stable ROCr UUID |
|
| 116 |
+
| `--mode` | `gundam` | `gundam` for detailed single-page parsing; `base` for lower-detail input |
|
| 117 |
+
| `--max-length` | `4096` | Total input + output sequence limit; maximum `32768` |
|
| 118 |
+
| `--dpi` | `200` | PDF rendering resolution |
|
| 119 |
+
| `--max-pages` | `20` | Per-run PDF safety cap |
|
| 120 |
+
| `--max-page-pixels` | `60000000` | Per-page rendered-pixel cap |
|
| 121 |
+
| `--max-total-pixels` | `400000000` | Aggregate rendered-pixel cap |
|
| 122 |
+
| `--max-page-rendered-mib` | `512` | Per-page rendered temporary-byte cap |
|
| 123 |
+
| `--max-rendered-mib` | `2048` | Aggregate rendered temporary-byte cap |
|
| 124 |
+
| `--force` | off | Replace an existing output file |
|
| 125 |
+
| `--json` | off | Stable machine-readable summary on stdout |
|
| 126 |
+
| `--quiet` | off | Suppress progress diagnostics |
|
| 127 |
+
|
| 128 |
+
OCR content is written to the output file. Progress and warnings go to stderr. Human or JSON summaries go to stdout.
|
| 129 |
+
|
| 130 |
+
## GPU selection
|
| 131 |
+
|
| 132 |
+
PyTorch uses the CUDA-compatible API name on ROCm. `--device` sets `ROCR_VISIBLE_DEVICES` before PyTorch is imported, so
|
| 133 |
+
the process sees one logical `cuda:0` backed by the selected AMD card.
|
| 134 |
+
|
| 135 |
+
For ordinary one-GPU systems:
|
| 136 |
+
|
| 137 |
+
```bash
|
| 138 |
+
unlimited-ocr-rdna4 run --input page.png --device 0
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
For multi-GPU systems, a stable ROCr UUID avoids dependence on enumeration order:
|
| 142 |
+
|
| 143 |
+
```bash
|
| 144 |
+
unlimited-ocr-rdna4 run --input page.png --device GPU-0123456789abcdef
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
Run `unlimited-ocr-rdna4 doctor` before choosing a device. Do not assume another application's GPU numbering matches
|
| 148 |
+
ROCm's ordinal numbering.
|
| 149 |
+
|
| 150 |
+
## What the RDNA 4 adaptation changes
|
| 151 |
+
|
| 152 |
+
- Pins the Baidu checkpoint and verifies the complete expected local model tree before custom code loads.
|
| 153 |
+
- Uses AMD's production ROCm 7.2.1 PyTorch, torchvision, and Triton wheels for Python 3.12.
|
| 154 |
+
- Isolates one `gfx1200`/`gfx1201` GPU before importing PyTorch.
|
| 155 |
+
- Replaces unsafe `eval()` calls in optional model-output geometry parsing with `ast.literal_eval()`.
|
| 156 |
+
- Makes one internal mask transfer follow the active tensor device.
|
| 157 |
+
- Supplies the missing all-ones attention mask and pad token for single-sequence generation.
|
| 158 |
+
- Loads only the prepared local model during inference and enables Hugging Face offline mode.
|
| 159 |
+
- Avoids vLLM, SGLang, quantization, tensor parallelism, and unverified custom serving kernels.
|
| 160 |
+
- Removes only complete, exact layout sentinel pairs without rewriting recognized Unicode or TeX.
|
| 161 |
+
- Detects obvious terminal repetition and warns without silently rewriting recognition content.
|
| 162 |
+
|
| 163 |
+
The model weights and mathematical operators are unchanged. PyTorch's ROCm backend provides the RDNA 4 kernels.
|
| 164 |
+
|
| 165 |
+
## Configuration
|
| 166 |
+
|
| 167 |
+
Precedence is flags, then environment variables, then XDG defaults.
|
| 168 |
+
|
| 169 |
+
| Environment variable | Purpose |
|
| 170 |
+
|---|---|
|
| 171 |
+
| `UNLIMITED_OCR_DEVICE` | Default ROCm ordinal or UUID |
|
| 172 |
+
| `UNLIMITED_OCR_MODEL_DIR` | Prepared model directory |
|
| 173 |
+
| `XDG_DATA_HOME` | Default model storage root |
|
| 174 |
+
| `XDG_CACHE_HOME` | Wheel, Hugging Face, and temporary-work cache root |
|
| 175 |
+
| `NO_COLOR` | Accepted implicitly; the CLI currently emits no color |
|
| 176 |
+
|
| 177 |
+
This project contains no analytics or telemetry code.
|
| 178 |
+
|
| 179 |
+
## Safety and limitations
|
| 180 |
+
|
| 181 |
+
- The default 4096 sequence limit includes visual-prefill tokens. Dense pages may require a larger value.
|
| 182 |
+
- PDF mode uses permissively licensed PDFium bindings and renders one bounded page at a time. A failed run leaves no
|
| 183 |
+
published partial document; retry with `--start-page` to resume manually.
|
| 184 |
+
- Generative OCR can omit or hallucinate content. Verify consequential documents against the source.
|
| 185 |
+
- Rotated text and repetitive pages are known upstream weak spots.
|
| 186 |
+
- Only the configuration above has completed the full repository acceptance test. Newer ROCm/PyTorch versions may work,
|
| 187 |
+
but `doctor` reports them as unverified until measured.
|
| 188 |
+
- The hardware guard accepts `gfx1200`, but reports `hardware_verified=false`; only `gfx1201` has completed this GPU gate.
|
| 189 |
+
- The bootstrap currently supports Linux x86_64, Python 3.12, and host ROCm 7.2.1.
|
| 190 |
+
|
| 191 |
+
See [SECURITY.md](SECURITY.md) before processing untrusted documents.
|
| 192 |
+
|
| 193 |
+
## Development
|
| 194 |
+
|
| 195 |
+
CPU-only tests do not install PyTorch. The commands below are convenient for development; the release bootstrap is the
|
| 196 |
+
hash-locked installation path.
|
| 197 |
+
|
| 198 |
+
```bash
|
| 199 |
+
python3.12 -m venv .venv-dev
|
| 200 |
+
.venv-dev/bin/pip install -e '.[dev]'
|
| 201 |
+
.venv-dev/bin/ruff check .
|
| 202 |
+
.venv-dev/bin/ruff format --check .
|
| 203 |
+
.venv-dev/bin/pytest
|
| 204 |
+
.venv-dev/bin/python -m build
|
| 205 |
+
.venv-dev/bin/twine check dist/*
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
The GPU smoke gate is intentionally separate. It uses tracked, hash-checked fixtures; parses structured JSON; runs the
|
| 209 |
+
image twice in fresh processes; and checks image/PDF output hashes, structure, stack identity, revision, VRAM, and process
|
| 210 |
+
release. On the validated host, run it in a root-created private network namespace:
|
| 211 |
+
|
| 212 |
+
```bash
|
| 213 |
+
VALIDATION_NETWORK_ISOLATED=1 \
|
| 214 |
+
UNLIMITED_OCR_DEVICE=GPU-0123456789abcdef \
|
| 215 |
+
AMD_SMI_GPU=3 \
|
| 216 |
+
./scripts/validate-smoke.sh
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
## Credits and license
|
| 220 |
+
|
| 221 |
+
- Model and model code: [Baidu Unlimited-OCR](https://github.com/baidu/Unlimited-OCR), MIT licensed.
|
| 222 |
+
- ROCm evaluation and batching research: [AIwork4me/Unlimited-OCR-ROCm](https://github.com/AIwork4me/Unlimited-OCR-ROCm).
|
| 223 |
+
- AMD wheel source and compatibility guidance: [ROCm documentation](https://rocm.docs.amd.com/).
|
| 224 |
+
- PDF rendering: [pypdfium2](https://pypi.org/project/pypdfium2/) and PDFium, under permissive licenses.
|
| 225 |
+
|
| 226 |
+
The runtime is MIT licensed. Baidu's original copyright and license are preserved; see
|
| 227 |
+
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
SECURITY.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Security policy
|
| 2 |
+
|
| 3 |
+
## Custom model code
|
| 4 |
+
|
| 5 |
+
Unlimited-OCR requires custom Python. This project never follows a floating model revision during inference.
|
| 6 |
+
|
| 7 |
+
`prepare`:
|
| 8 |
+
|
| 9 |
+
1. downloads one pinned Baidu revision into a unique partial directory under a preparation lock;
|
| 10 |
+
2. fully hashes every expected code, configuration, tokenizer, index, license, and weight file;
|
| 11 |
+
3. applies exact, count-checked source transformations;
|
| 12 |
+
4. verifies the patched tree, rejects symlinks and unexpected files, and records an exact local manifest;
|
| 13 |
+
5. publishes the completed directory only after all checks pass.
|
| 14 |
+
|
| 15 |
+
`run` uses only the prepared local directory with `local_files_only=True`, `HF_HUB_OFFLINE=1`, and
|
| 16 |
+
`TRANSFORMERS_OFFLINE=1`. Those settings prevent model resolution from using the network; they are not a process sandbox.
|
| 17 |
+
|
| 18 |
+
## Untrusted documents
|
| 19 |
+
|
| 20 |
+
Model output is untrusted Markdown/raw HTML data. The runtime does not call `eval()` on it. Output paths use an exclusive,
|
| 21 |
+
random same-directory temporary file and an atomic no-replace publish; symlinks and input/output aliases are rejected.
|
| 22 |
+
`--force` alone enables atomic replacement.
|
| 23 |
+
|
| 24 |
+
Images and PDFs can still trigger bugs in Pillow, PDFium/pypdfium2, PyTorch, Transformers, or the model code. PDF rendering
|
| 25 |
+
has per-page and aggregate pixel/byte limits, but hostile inputs still belong under a dedicated unprivileged account or
|
| 26 |
+
an appropriately restricted container. Do not expose this CLI directly as a public upload service without process/network
|
| 27 |
+
sandboxing, input-byte limits, timeouts, and admission controls.
|
| 28 |
+
|
| 29 |
+
## Reporting
|
| 30 |
+
|
| 31 |
+
Please report vulnerabilities privately through GitHub's security-advisory interface for this repository. Do not include
|
| 32 |
+
private documents, credentials, model-cache tokens, or sensitive OCR output in a report.
|
THIRD_PARTY_NOTICES.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Third-party notices
|
| 2 |
+
|
| 3 |
+
## Baidu Unlimited-OCR
|
| 4 |
+
|
| 5 |
+
The `prepare` command downloads and modifies one source file from
|
| 6 |
+
[`baidu/Unlimited-OCR`](https://huggingface.co/baidu/Unlimited-OCR) revision
|
| 7 |
+
`07dea832e22aefee32ad281d4b80551282e1c168`. The original source is preserved beside the patched file as
|
| 8 |
+
`modeling_unlimitedocr.py.upstream`.
|
| 9 |
+
|
| 10 |
+
MIT License
|
| 11 |
+
|
| 12 |
+
Copyright (c) 2026 Baidu
|
| 13 |
+
|
| 14 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 15 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 16 |
+
in the Software without restriction, including without limitation the rights
|
| 17 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 18 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 19 |
+
furnished to do so, subject to the following conditions:
|
| 20 |
+
|
| 21 |
+
The above copyright notice and this permission notice shall be included in all
|
| 22 |
+
copies or substantial portions of the Software.
|
| 23 |
+
|
| 24 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 25 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 26 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 27 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 28 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 29 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 30 |
+
SOFTWARE.
|
| 31 |
+
|
| 32 |
+
## Unlimited-OCR-ROCm
|
| 33 |
+
|
| 34 |
+
The project does not copy its package code. Its public ROCm evaluation, accuracy analysis, and direct-Transformers
|
| 35 |
+
research informed this runtime and are credited in the README.
|
| 36 |
+
|
| 37 |
+
## pypdfium2 and PDFium
|
| 38 |
+
|
| 39 |
+
PDF support depends on [`pypdfium2`](https://github.com/pypdfium2-team/pypdfium2), which is available under
|
| 40 |
+
BSD-3-Clause or Apache-2.0 terms and distributes PDFium plus its dependency license notices. PDFium itself uses a
|
| 41 |
+
BSD-style license. This repository does not vendor either binary; the hash-locked bootstrap installs the official
|
| 42 |
+
pypdfium2 wheel, which carries its applicable notices.
|
dist/unlimited_ocr_rdna4-0.1.0-py3-none-any.whl
ADDED
|
Binary file (24.7 kB). View file
|
|
|
dist/unlimited_ocr_rdna4-0.1.0.tar.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c2146c24804ef7d6633156e7d48010b40c08f6ce04a29bd40b404455bb751d3c
|
| 3 |
+
size 252988
|
docs/PUBLISHING.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Publishing checklist
|
| 2 |
+
|
| 3 |
+
The repository intentionally separates source publication from package/model publication.
|
| 4 |
+
|
| 5 |
+
## Hugging Face
|
| 6 |
+
|
| 7 |
+
1. Review `git diff --check`, tests, build artifacts, and validation evidence.
|
| 8 |
+
2. Create `dougvk/Unlimited-OCR-RDNA4` as a private model repository.
|
| 9 |
+
3. Push the reviewed initial commit.
|
| 10 |
+
4. Verify the model card, source tree, package artifacts, and remote hashes.
|
| 11 |
+
5. Make the repository public only after those checks pass.
|
| 12 |
+
6. Tag `v0.1.0` only after the clean-environment gate in `docs/VALIDATION.md` is reproduced.
|
| 13 |
+
|
| 14 |
+
## PyPI (deferred)
|
| 15 |
+
|
| 16 |
+
Do not publish `v0.1.0` to PyPI. The supported installation contract is the repository's hash-locked bootstrap, while an
|
| 17 |
+
installed wheel cannot yet reproduce that bootstrap without returning to the source checkout. Building and running
|
| 18 |
+
`twine check` remains a packaging gate, not authorization to upload. A later PyPI release should bundle an equivalent
|
| 19 |
+
bootstrap entry point and independently verify a fresh wheel installation first.
|
| 20 |
+
|
| 21 |
+
Do not upload or duplicate Baidu's checkpoint. The repository contains the runtime source and packages and links to the
|
| 22 |
+
original `baidu/Unlimited-OCR` weights.
|
| 23 |
+
|
| 24 |
+
The repository should be classified as documentation for a runtime/integration, not as a runnable Transformers
|
| 25 |
+
pipeline, new model, checkpoint, fine-tune, or kernel port.
|
| 26 |
+
|
| 27 |
+
## GitHub (optional mirror)
|
| 28 |
+
|
| 29 |
+
If a GitHub mirror is created later, update the project URLs consistently and enable private vulnerability reporting.
|
| 30 |
+
|
| 31 |
+
## Upstream
|
| 32 |
+
|
| 33 |
+
After publication:
|
| 34 |
+
|
| 35 |
+
1. open a concise Baidu discussion or documentation pull request with the verified configuration and evidence;
|
| 36 |
+
2. offer the gfx1201 bootstrap and validation findings to `AIwork4me/Unlimited-OCR-ROCm`;
|
| 37 |
+
3. avoid claiming universal ROCm or all-RDNA support until independently reproduced.
|
docs/VALIDATION.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# RDNA 4 validation
|
| 2 |
+
|
| 3 |
+
## Scope
|
| 4 |
+
|
| 5 |
+
This gate validates a narrow runtime integration:
|
| 6 |
+
|
| 7 |
+
- original Baidu BF16 checkpoint at one immutable revision;
|
| 8 |
+
- direct Hugging Face Transformers inference on one visible AMD GPU;
|
| 9 |
+
- no quantization, serving framework, tensor parallelism, custom kernel package, renamed weights, or fine-tuning;
|
| 10 |
+
- exact software inventory, model-tree integrity, deterministic output, bounded PDF rendering, and released VRAM.
|
| 11 |
+
|
| 12 |
+
It is not a new model/checkpoint/kernel port or an OCR-quality benchmark.
|
| 13 |
+
|
| 14 |
+
## Verified host
|
| 15 |
+
|
| 16 |
+
Validated 31 July 2026 on one GPU in a Tinybox Red v2:
|
| 17 |
+
|
| 18 |
+
- AMD Radeon RX 9070 XT, `gfx1201`, 16 GB;
|
| 19 |
+
- Ubuntu 24.04.4, kernel `6.17.0-40-generic`;
|
| 20 |
+
- host ROCm 7.2.1 and HIP runtime `7.2.53211-e1a6bc5663`;
|
| 21 |
+
- PyTorch `2.9.1+rocm7.2.1.gitff65f5bc`;
|
| 22 |
+
- torchvision `0.24.0+rocm7.2.1.gitb919bd0c`;
|
| 23 |
+
- Triton `3.5.1+rocm7.2.1.gita272dfa8`;
|
| 24 |
+
- Transformers 4.57.1 and pypdfium2 5.12.1;
|
| 25 |
+
- model revision `07dea832e22aefee32ad281d4b80551282e1c168`;
|
| 26 |
+
- weight SHA-256 `2bc48a7a110061ea58fff65d3169367eebe3aee371ca6968dc2219c1b2855fc6`.
|
| 27 |
+
|
| 28 |
+
The exact parsed evidence is checked in as [`validation-gfx1201.json`](validation-gfx1201.json).
|
| 29 |
+
|
| 30 |
+
## Tracked gate
|
| 31 |
+
|
| 32 |
+
The repository carries one synthetic 1600×2000 document as PNG and one-page PDF. Their bytes are fixed:
|
| 33 |
+
|
| 34 |
+
| Fixture | SHA-256 |
|
| 35 |
+
|---|---|
|
| 36 |
+
| PNG | `f5099e17be868abfb4213dbdab220deac82a2db93ba87ab22f03219178246972` |
|
| 37 |
+
| PDF | `cdd2b484d0ac90bd98b489dd97565a65eb17246f713359524cddd41d78cc10cb` |
|
| 38 |
+
|
| 39 |
+
The gate:
|
| 40 |
+
|
| 41 |
+
1. checksum-verifies both fixtures;
|
| 42 |
+
2. parses `doctor --json` and requires exactly one visible `gfx1201`, BF16, the exact package/HIP stack, and the pinned prepared model;
|
| 43 |
+
3. proves the root-created systemd `PrivateNetwork` namespace cannot make an outbound connection;
|
| 44 |
+
4. runs PNG inference twice in fresh processes and PDF inference once;
|
| 45 |
+
5. checks heading/table/reading order, euro amount, formula, checksum text, page counts, revision, architecture, and a sub-16-GiB peak;
|
| 46 |
+
6. requires byte-identical repeated PNG output and the recorded PNG/PDF output hashes;
|
| 47 |
+
7. parses AMD SMI JSON after completion and rejects a remaining Python GPU process.
|
| 48 |
+
|
| 49 |
+
Final results:
|
| 50 |
+
|
| 51 |
+
| Test | Model load | Inference | Peak allocated VRAM | Output SHA-256 |
|
| 52 |
+
|---|---:|---:|---:|---|
|
| 53 |
+
| PNG process 1 | 9.287 s | 15.275 s | 8.253 GiB | `13df4005…cbc01` |
|
| 54 |
+
| PNG process 2 | 9.000 s | 14.995 s | 8.253 GiB | `13df4005…cbc01` |
|
| 55 |
+
| One-page PDF, 150 DPI | 8.983 s | 15.368 s | 8.253 GiB | `fb17a639…42438` |
|
| 56 |
+
|
| 57 |
+
AMD SMI reported `No running processes detected` after the final process. The two PNG results were byte-identical.
|
| 58 |
+
|
| 59 |
+
## Reproduce
|
| 60 |
+
|
| 61 |
+
Start with no competing owner of the selected GPU, then run:
|
| 62 |
+
|
| 63 |
+
```bash
|
| 64 |
+
./scripts/bootstrap-rocm.sh
|
| 65 |
+
.venv/bin/unlimited-ocr-rdna4 prepare
|
| 66 |
+
|
| 67 |
+
VALIDATION_NETWORK_ISOLATED=1 \
|
| 68 |
+
UNLIMITED_OCR_DEVICE=GPU-0123456789abcdef \
|
| 69 |
+
AMD_SMI_GPU=3 \
|
| 70 |
+
./scripts/validate-smoke.sh
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
`VALIDATION_NETWORK_ISOLATED=1` requires non-interactive permission for the script's narrowly scoped root
|
| 74 |
+
`systemd-run` command. Without it, the functional gate still runs but correctly records `network_isolated=false`.
|
| 75 |
+
`AMD_SMI_GPU` is the physical AMD SMI index used only for the post-run process check.
|
| 76 |
+
|
| 77 |
+
Transformers reports `model.vision_model.embeddings.position_ids` as newly initialized. That object is a non-persistent
|
| 78 |
+
derived position-index buffer, not a learned checkpoint parameter; the original safetensors file is fully hashed before
|
| 79 |
+
every load.
|
| 80 |
+
|
| 81 |
+
## Limits
|
| 82 |
+
|
| 83 |
+
- Only `gfx1201` has passed this exact gate. `gfx1200` is accepted by the architecture guard but reported as not hardware
|
| 84 |
+
verified.
|
| 85 |
+
- The synthetic fixture demonstrates runtime compatibility, deterministic decoding, basic structure recovery, and the
|
| 86 |
+
PDF path. It does not establish real-document accuracy.
|
| 87 |
+
- Generative OCR can omit or hallucinate text. Consequential output must be checked against the source.
|
docs/validation-gfx1201.json
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"doctor": {
|
| 3 |
+
"model": {
|
| 4 |
+
"model_dir": "<prepared-model>",
|
| 5 |
+
"prepared": true,
|
| 6 |
+
"revision": "07dea832e22aefee32ad281d4b80551282e1c168"
|
| 7 |
+
},
|
| 8 |
+
"runtime": {
|
| 9 |
+
"accepted_architecture": true,
|
| 10 |
+
"bf16_supported": true,
|
| 11 |
+
"cuda_available": true,
|
| 12 |
+
"devices": [
|
| 13 |
+
{
|
| 14 |
+
"architecture": "gfx1201",
|
| 15 |
+
"index": 0,
|
| 16 |
+
"name": "AMD Radeon RX 9070 XT",
|
| 17 |
+
"uuid": "39663934-3932-3539-3931-333733663736"
|
| 18 |
+
}
|
| 19 |
+
],
|
| 20 |
+
"hardware_verified": true,
|
| 21 |
+
"hip_version": "7.2.53211-e1a6bc5663",
|
| 22 |
+
"package_versions": {
|
| 23 |
+
"numpy": "1.26.4",
|
| 24 |
+
"pillow": "12.1.1",
|
| 25 |
+
"pypdfium2": "5.12.1",
|
| 26 |
+
"torch": "2.9.1+rocm7.2.1.lw.gitff65f5bc",
|
| 27 |
+
"torchvision": "0.24.0+rocm7.2.1.gitb919bd0c",
|
| 28 |
+
"transformers": "4.57.1",
|
| 29 |
+
"triton": "3.5.1+rocm7.2.1.gita272dfa8"
|
| 30 |
+
},
|
| 31 |
+
"software_verified": true,
|
| 32 |
+
"torch_version": "2.9.1+rocm7.2.1.gitff65f5bc",
|
| 33 |
+
"visible_devices": 1,
|
| 34 |
+
"warnings": []
|
| 35 |
+
},
|
| 36 |
+
"runtime_issues": [],
|
| 37 |
+
"schema_version": 1,
|
| 38 |
+
"status": "ok"
|
| 39 |
+
},
|
| 40 |
+
"fixtures": {
|
| 41 |
+
"image_sha256": "f5099e17be868abfb4213dbdab220deac82a2db93ba87ab22f03219178246972",
|
| 42 |
+
"pdf_sha256": "cdd2b484d0ac90bd98b489dd97565a65eb17246f713359524cddd41d78cc10cb"
|
| 43 |
+
},
|
| 44 |
+
"gpu_processes_after": [
|
| 45 |
+
{
|
| 46 |
+
"gpu": 3,
|
| 47 |
+
"process_list": [
|
| 48 |
+
{
|
| 49 |
+
"process_info": "No running processes detected"
|
| 50 |
+
}
|
| 51 |
+
]
|
| 52 |
+
}
|
| 53 |
+
],
|
| 54 |
+
"image_runs": [
|
| 55 |
+
{
|
| 56 |
+
"architecture": "gfx1201",
|
| 57 |
+
"gpu": "AMD Radeon RX 9070 XT",
|
| 58 |
+
"hardware_verified": true,
|
| 59 |
+
"hip_version": "7.2.53211-e1a6bc5663",
|
| 60 |
+
"inference_seconds": 15.275,
|
| 61 |
+
"input": "tests/fixtures/rdna4-smoke.png",
|
| 62 |
+
"mode": "gundam",
|
| 63 |
+
"model_load_seconds": 9.287,
|
| 64 |
+
"model_revision": "07dea832e22aefee32ad281d4b80551282e1c168",
|
| 65 |
+
"output": "<temporary-output>/smoke-image-1.md",
|
| 66 |
+
"page_seconds": [
|
| 67 |
+
15.274
|
| 68 |
+
],
|
| 69 |
+
"pages_processed": 1,
|
| 70 |
+
"pages_total": 1,
|
| 71 |
+
"peak_allocated_gib": 8.253,
|
| 72 |
+
"schema_version": 1,
|
| 73 |
+
"software_verified": true,
|
| 74 |
+
"status": "ok",
|
| 75 |
+
"torch_version": "2.9.1+rocm7.2.1.gitff65f5bc",
|
| 76 |
+
"warnings": []
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"architecture": "gfx1201",
|
| 80 |
+
"gpu": "AMD Radeon RX 9070 XT",
|
| 81 |
+
"hardware_verified": true,
|
| 82 |
+
"hip_version": "7.2.53211-e1a6bc5663",
|
| 83 |
+
"inference_seconds": 14.995,
|
| 84 |
+
"input": "tests/fixtures/rdna4-smoke.png",
|
| 85 |
+
"mode": "gundam",
|
| 86 |
+
"model_load_seconds": 9.0,
|
| 87 |
+
"model_revision": "07dea832e22aefee32ad281d4b80551282e1c168",
|
| 88 |
+
"output": "<temporary-output>/smoke-image-2.md",
|
| 89 |
+
"page_seconds": [
|
| 90 |
+
14.995
|
| 91 |
+
],
|
| 92 |
+
"pages_processed": 1,
|
| 93 |
+
"pages_total": 1,
|
| 94 |
+
"peak_allocated_gib": 8.253,
|
| 95 |
+
"schema_version": 1,
|
| 96 |
+
"software_verified": true,
|
| 97 |
+
"status": "ok",
|
| 98 |
+
"torch_version": "2.9.1+rocm7.2.1.gitff65f5bc",
|
| 99 |
+
"warnings": []
|
| 100 |
+
}
|
| 101 |
+
],
|
| 102 |
+
"image_sha256": "13df40051aceb3bd14f75622697dda1f2a96515bb95c0483e0920cd4d41cbc01",
|
| 103 |
+
"network_isolated": true,
|
| 104 |
+
"pdf_run": {
|
| 105 |
+
"architecture": "gfx1201",
|
| 106 |
+
"gpu": "AMD Radeon RX 9070 XT",
|
| 107 |
+
"hardware_verified": true,
|
| 108 |
+
"hip_version": "7.2.53211-e1a6bc5663",
|
| 109 |
+
"inference_seconds": 15.368,
|
| 110 |
+
"input": "tests/fixtures/rdna4-smoke.pdf",
|
| 111 |
+
"mode": "gundam",
|
| 112 |
+
"model_load_seconds": 8.983,
|
| 113 |
+
"model_revision": "07dea832e22aefee32ad281d4b80551282e1c168",
|
| 114 |
+
"output": "<temporary-output>/smoke-pdf.md",
|
| 115 |
+
"page_seconds": [
|
| 116 |
+
15.015
|
| 117 |
+
],
|
| 118 |
+
"pages_processed": 1,
|
| 119 |
+
"pages_total": 1,
|
| 120 |
+
"peak_allocated_gib": 8.253,
|
| 121 |
+
"schema_version": 1,
|
| 122 |
+
"software_verified": true,
|
| 123 |
+
"status": "ok",
|
| 124 |
+
"torch_version": "2.9.1+rocm7.2.1.gitff65f5bc",
|
| 125 |
+
"warnings": []
|
| 126 |
+
},
|
| 127 |
+
"pdf_sha256": "fb17a6396c3e73a025e388a09f73357960ca0bf95124e38179e06a2e64442438",
|
| 128 |
+
"schema_version": 1,
|
| 129 |
+
"validation": "PASS"
|
| 130 |
+
}
|
huggingface/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
base_model: baidu/Unlimited-OCR
|
| 3 |
+
license: mit
|
| 4 |
+
tags:
|
| 5 |
+
- rocm
|
| 6 |
+
- amd
|
| 7 |
+
- rdna4
|
| 8 |
+
- gfx1201
|
| 9 |
+
- ocr
|
| 10 |
+
- runtime
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
# Unlimited-OCR RDNA 4 runtime
|
| 14 |
+
|
| 15 |
+
This is documentation for a lightweight runtime companion to
|
| 16 |
+
[`baidu/Unlimited-OCR`](https://huggingface.co/baidu/Unlimited-OCR), not a hosted pipeline, new checkpoint, fine-tune, or
|
| 17 |
+
kernel port. No model weights are duplicated here.
|
| 18 |
+
|
| 19 |
+
The linked runtime verifies direct BF16 Transformers inference on one 16 GB AMD Radeon RX 9070 XT (`gfx1201`) with ROCm
|
| 20 |
+
7.2.1 and AMD's PyTorch 2.9.1 wheel set. It provides pinned model preparation, code-integrity checks, offline inference,
|
| 21 |
+
bounded PDF handling, and tracked reproducible validation fixtures.
|
| 22 |
+
|
| 23 |
+
Source and instructions: <https://huggingface.co/dougvk/Unlimited-OCR-RDNA4>
|
| 24 |
+
|
| 25 |
+
Please cite and follow the license terms of the original Baidu model.
|
pyproject.toml
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["hatchling==1.27.0"]
|
| 3 |
+
build-backend = "hatchling.build"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "unlimited-ocr-rdna4"
|
| 7 |
+
dynamic = ["version"]
|
| 8 |
+
description = "Verified local Unlimited-OCR inference on AMD RDNA 4 with ROCm"
|
| 9 |
+
readme = "README.md"
|
| 10 |
+
requires-python = ">=3.12,<3.13"
|
| 11 |
+
license = {file = "LICENSE"}
|
| 12 |
+
authors = [{name = "Douglas von Kohorn"}]
|
| 13 |
+
keywords = ["ocr", "rocm", "rdna4", "gfx1201", "amd", "transformers"]
|
| 14 |
+
classifiers = [
|
| 15 |
+
"Development Status :: 4 - Beta",
|
| 16 |
+
"Environment :: Console",
|
| 17 |
+
"Intended Audience :: Developers",
|
| 18 |
+
"Intended Audience :: Science/Research",
|
| 19 |
+
"License :: OSI Approved :: MIT License",
|
| 20 |
+
"Operating System :: POSIX :: Linux",
|
| 21 |
+
"Programming Language :: Python :: 3",
|
| 22 |
+
"Programming Language :: Python :: 3.12",
|
| 23 |
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
| 24 |
+
]
|
| 25 |
+
dependencies = [
|
| 26 |
+
"addict==2.4.0",
|
| 27 |
+
"easydict==1.13",
|
| 28 |
+
"einops==0.8.2",
|
| 29 |
+
"huggingface-hub==0.36.2",
|
| 30 |
+
"matplotlib==3.10.8",
|
| 31 |
+
"numpy==1.26.4",
|
| 32 |
+
"Pillow==12.1.1",
|
| 33 |
+
"psutil==7.2.2",
|
| 34 |
+
"pypdfium2==5.12.1",
|
| 35 |
+
"requests==2.34.2",
|
| 36 |
+
"safetensors==0.8.0",
|
| 37 |
+
"tqdm==4.70.0",
|
| 38 |
+
"transformers==4.57.1",
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
[project.optional-dependencies]
|
| 42 |
+
dev = ["build==1.5.0", "pytest==8.4.2", "pytest-cov==6.3.0", "ruff==0.14.14", "twine==6.2.0"]
|
| 43 |
+
|
| 44 |
+
[project.scripts]
|
| 45 |
+
unlimited-ocr-rdna4 = "unlimited_ocr_rdna4.cli:main"
|
| 46 |
+
|
| 47 |
+
[project.urls]
|
| 48 |
+
Homepage = "https://huggingface.co/dougvk/Unlimited-OCR-RDNA4"
|
| 49 |
+
Documentation = "https://huggingface.co/dougvk/Unlimited-OCR-RDNA4#readme"
|
| 50 |
+
Issues = "https://huggingface.co/dougvk/Unlimited-OCR-RDNA4/discussions"
|
| 51 |
+
Source = "https://huggingface.co/dougvk/Unlimited-OCR-RDNA4"
|
| 52 |
+
|
| 53 |
+
[tool.hatch.build.targets.wheel]
|
| 54 |
+
packages = ["src/unlimited_ocr_rdna4"]
|
| 55 |
+
|
| 56 |
+
[tool.hatch.version]
|
| 57 |
+
path = "src/unlimited_ocr_rdna4/constants.py"
|
| 58 |
+
|
| 59 |
+
[tool.pytest.ini_options]
|
| 60 |
+
addopts = "-q"
|
| 61 |
+
testpaths = ["tests"]
|
| 62 |
+
|
| 63 |
+
[tool.ruff]
|
| 64 |
+
line-length = 120
|
| 65 |
+
target-version = "py312"
|
| 66 |
+
|
| 67 |
+
[tool.ruff.lint]
|
| 68 |
+
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
| 69 |
+
allowed-confusables = ["|"]
|
| 70 |
+
|
| 71 |
+
[tool.ruff.format]
|
| 72 |
+
quote-style = "double"
|
requirements/bootstrap.in
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Runtime dependencies: keep direct pins synchronized with pyproject.toml.
|
| 2 |
+
addict==2.4.0
|
| 3 |
+
easydict==1.13
|
| 4 |
+
einops==0.8.2
|
| 5 |
+
huggingface-hub==0.36.2
|
| 6 |
+
matplotlib==3.10.8
|
| 7 |
+
numpy==1.26.4
|
| 8 |
+
Pillow==12.1.1
|
| 9 |
+
psutil==7.2.2
|
| 10 |
+
pypdfium2==5.12.1
|
| 11 |
+
requests==2.34.2
|
| 12 |
+
safetensors==0.8.0
|
| 13 |
+
tqdm==4.70.0
|
| 14 |
+
transformers==4.57.1
|
| 15 |
+
|
| 16 |
+
# Build backend used with --no-build-isolation.
|
| 17 |
+
hatchling==1.27.0
|
| 18 |
+
|
| 19 |
+
# Dependencies declared by AMD's checksum-pinned PyTorch wheel.
|
| 20 |
+
filelock==3.32.2
|
| 21 |
+
fsspec==2026.7.0
|
| 22 |
+
Jinja2==3.1.6
|
| 23 |
+
networkx==3.6.1
|
| 24 |
+
setuptools==83.0.0
|
| 25 |
+
sympy==1.14.0
|
| 26 |
+
typing-extensions==4.16.0
|
requirements/bootstrap.lock
ADDED
|
@@ -0,0 +1,1061 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file was autogenerated by uv via the following command:
|
| 2 |
+
# uv pip compile requirements/bootstrap.in --generate-hashes --only-binary=:all: --python-version 3.12 --python-platform x86_64-manylinux_2_28 -o requirements/bootstrap.lock
|
| 3 |
+
addict==2.4.0 \
|
| 4 |
+
--hash=sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc \
|
| 5 |
+
--hash=sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494
|
| 6 |
+
# via -r requirements/bootstrap.in
|
| 7 |
+
certifi==2026.7.22 \
|
| 8 |
+
--hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775 \
|
| 9 |
+
--hash=sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55
|
| 10 |
+
# via requests
|
| 11 |
+
charset-normalizer==3.4.9 \
|
| 12 |
+
--hash=sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380 \
|
| 13 |
+
--hash=sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62 \
|
| 14 |
+
--hash=sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c \
|
| 15 |
+
--hash=sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226 \
|
| 16 |
+
--hash=sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5 \
|
| 17 |
+
--hash=sha256:0fa1aec2d32bcc03c8fa0f6f1712caad1adc38509f31142112e5c9daf5b9c833 \
|
| 18 |
+
--hash=sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b \
|
| 19 |
+
--hash=sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99 \
|
| 20 |
+
--hash=sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501 \
|
| 21 |
+
--hash=sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec \
|
| 22 |
+
--hash=sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698 \
|
| 23 |
+
--hash=sha256:231ddcbb35e2ff8973e1365db41fe0572662893b99a05deb183b68ad4c0c8bd4 \
|
| 24 |
+
--hash=sha256:253a4a220747e8b5faf57ec320c4f5efb0cef05f647420bf267143ec15dba10a \
|
| 25 |
+
--hash=sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3 \
|
| 26 |
+
--hash=sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2 \
|
| 27 |
+
--hash=sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a \
|
| 28 |
+
--hash=sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e \
|
| 29 |
+
--hash=sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4 \
|
| 30 |
+
--hash=sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419 \
|
| 31 |
+
--hash=sha256:3c09a49d6cde137258beb3d551994a2927fd35ad5cf96aed573f61bbd67c5f84 \
|
| 32 |
+
--hash=sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da \
|
| 33 |
+
--hash=sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519 \
|
| 34 |
+
--hash=sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe \
|
| 35 |
+
--hash=sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381 \
|
| 36 |
+
--hash=sha256:43b9e366a31fdd1c87d0eb08f579b4a82b723ea54338f040d6b4e518a026ea29 \
|
| 37 |
+
--hash=sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614 \
|
| 38 |
+
--hash=sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0 \
|
| 39 |
+
--hash=sha256:476743fe6dfe14a2da12e3ac79125dc84a3b2cf8094369a47a1529b0cd8549fe \
|
| 40 |
+
--hash=sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29 \
|
| 41 |
+
--hash=sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0 \
|
| 42 |
+
--hash=sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917 \
|
| 43 |
+
--hash=sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9 \
|
| 44 |
+
--hash=sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32 \
|
| 45 |
+
--hash=sha256:58150c9f9b9a552505912d182ccdf26f6396fb6094816ceebcbb20eecabaed94 \
|
| 46 |
+
--hash=sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63 \
|
| 47 |
+
--hash=sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd \
|
| 48 |
+
--hash=sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198 \
|
| 49 |
+
--hash=sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde \
|
| 50 |
+
--hash=sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012 \
|
| 51 |
+
--hash=sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1 \
|
| 52 |
+
--hash=sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15 \
|
| 53 |
+
--hash=sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b \
|
| 54 |
+
--hash=sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993 \
|
| 55 |
+
--hash=sha256:68ce9f4d6b26d5ccbf7fd4459bf75f74a0a146677ebba80597df60cbdb20e6f4 \
|
| 56 |
+
--hash=sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5 \
|
| 57 |
+
--hash=sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8 \
|
| 58 |
+
--hash=sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35 \
|
| 59 |
+
--hash=sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642 \
|
| 60 |
+
--hash=sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2 \
|
| 61 |
+
--hash=sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d \
|
| 62 |
+
--hash=sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9 \
|
| 63 |
+
--hash=sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c \
|
| 64 |
+
--hash=sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33 \
|
| 65 |
+
--hash=sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db \
|
| 66 |
+
--hash=sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf \
|
| 67 |
+
--hash=sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9 \
|
| 68 |
+
--hash=sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee \
|
| 69 |
+
--hash=sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84 \
|
| 70 |
+
--hash=sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44 \
|
| 71 |
+
--hash=sha256:920079c3f7456fa213e0829ed2073aaa727fd39d889ead5b4f35d0de5460d04f \
|
| 72 |
+
--hash=sha256:93d59d504b230e83c7a843251681959a0b6a9cd76f6e146ce1b8a80eb8739af9 \
|
| 73 |
+
--hash=sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9 \
|
| 74 |
+
--hash=sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177 \
|
| 75 |
+
--hash=sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8 \
|
| 76 |
+
--hash=sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b \
|
| 77 |
+
--hash=sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39 \
|
| 78 |
+
--hash=sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41 \
|
| 79 |
+
--hash=sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0 \
|
| 80 |
+
--hash=sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616 \
|
| 81 |
+
--hash=sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d \
|
| 82 |
+
--hash=sha256:ad41ba96094304aa090f5a30cb6e4fb3b3f1c264c523394b4c39bbacc4dc92ba \
|
| 83 |
+
--hash=sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2 \
|
| 84 |
+
--hash=sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b \
|
| 85 |
+
--hash=sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9 \
|
| 86 |
+
--hash=sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b \
|
| 87 |
+
--hash=sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209 \
|
| 88 |
+
--hash=sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48 \
|
| 89 |
+
--hash=sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046 \
|
| 90 |
+
--hash=sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632 \
|
| 91 |
+
--hash=sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a \
|
| 92 |
+
--hash=sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2 \
|
| 93 |
+
--hash=sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1 \
|
| 94 |
+
--hash=sha256:ddf4af30b417d9fe16481e9b81c27ab2a7cde1ff7ba3e85653b02db7d145dc7b \
|
| 95 |
+
--hash=sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990 \
|
| 96 |
+
--hash=sha256:df7276909358e5635ae203673ab7e509ddd224225a8d6b0790bf13eb2bde1cc5 \
|
| 97 |
+
--hash=sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b \
|
| 98 |
+
--hash=sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9 \
|
| 99 |
+
--hash=sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534 \
|
| 100 |
+
--hash=sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81 \
|
| 101 |
+
--hash=sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a \
|
| 102 |
+
--hash=sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d \
|
| 103 |
+
--hash=sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf \
|
| 104 |
+
--hash=sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115
|
| 105 |
+
# via requests
|
| 106 |
+
contourpy==1.3.3 \
|
| 107 |
+
--hash=sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69 \
|
| 108 |
+
--hash=sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc \
|
| 109 |
+
--hash=sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880 \
|
| 110 |
+
--hash=sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a \
|
| 111 |
+
--hash=sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8 \
|
| 112 |
+
--hash=sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc \
|
| 113 |
+
--hash=sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470 \
|
| 114 |
+
--hash=sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5 \
|
| 115 |
+
--hash=sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263 \
|
| 116 |
+
--hash=sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b \
|
| 117 |
+
--hash=sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5 \
|
| 118 |
+
--hash=sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381 \
|
| 119 |
+
--hash=sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3 \
|
| 120 |
+
--hash=sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4 \
|
| 121 |
+
--hash=sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e \
|
| 122 |
+
--hash=sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f \
|
| 123 |
+
--hash=sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772 \
|
| 124 |
+
--hash=sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286 \
|
| 125 |
+
--hash=sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42 \
|
| 126 |
+
--hash=sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301 \
|
| 127 |
+
--hash=sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77 \
|
| 128 |
+
--hash=sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7 \
|
| 129 |
+
--hash=sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411 \
|
| 130 |
+
--hash=sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1 \
|
| 131 |
+
--hash=sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9 \
|
| 132 |
+
--hash=sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a \
|
| 133 |
+
--hash=sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b \
|
| 134 |
+
--hash=sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db \
|
| 135 |
+
--hash=sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6 \
|
| 136 |
+
--hash=sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620 \
|
| 137 |
+
--hash=sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989 \
|
| 138 |
+
--hash=sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea \
|
| 139 |
+
--hash=sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67 \
|
| 140 |
+
--hash=sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5 \
|
| 141 |
+
--hash=sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d \
|
| 142 |
+
--hash=sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36 \
|
| 143 |
+
--hash=sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99 \
|
| 144 |
+
--hash=sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1 \
|
| 145 |
+
--hash=sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e \
|
| 146 |
+
--hash=sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b \
|
| 147 |
+
--hash=sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8 \
|
| 148 |
+
--hash=sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d \
|
| 149 |
+
--hash=sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7 \
|
| 150 |
+
--hash=sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7 \
|
| 151 |
+
--hash=sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339 \
|
| 152 |
+
--hash=sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1 \
|
| 153 |
+
--hash=sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659 \
|
| 154 |
+
--hash=sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4 \
|
| 155 |
+
--hash=sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f \
|
| 156 |
+
--hash=sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20 \
|
| 157 |
+
--hash=sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36 \
|
| 158 |
+
--hash=sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb \
|
| 159 |
+
--hash=sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d \
|
| 160 |
+
--hash=sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8 \
|
| 161 |
+
--hash=sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0 \
|
| 162 |
+
--hash=sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b \
|
| 163 |
+
--hash=sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7 \
|
| 164 |
+
--hash=sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe \
|
| 165 |
+
--hash=sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77 \
|
| 166 |
+
--hash=sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497 \
|
| 167 |
+
--hash=sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd \
|
| 168 |
+
--hash=sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1 \
|
| 169 |
+
--hash=sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216 \
|
| 170 |
+
--hash=sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13 \
|
| 171 |
+
--hash=sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae \
|
| 172 |
+
--hash=sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae \
|
| 173 |
+
--hash=sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77 \
|
| 174 |
+
--hash=sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3 \
|
| 175 |
+
--hash=sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f \
|
| 176 |
+
--hash=sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff \
|
| 177 |
+
--hash=sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9 \
|
| 178 |
+
--hash=sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a
|
| 179 |
+
# via matplotlib
|
| 180 |
+
cycler==0.12.1 \
|
| 181 |
+
--hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 \
|
| 182 |
+
--hash=sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c
|
| 183 |
+
# via matplotlib
|
| 184 |
+
easydict==1.13 \
|
| 185 |
+
--hash=sha256:6b787daf4dcaf6377b4ad9403a5cee5a86adbc0ca9a5bcf5410e9902002aeac2 \
|
| 186 |
+
--hash=sha256:b1135dedbc41c8010e2bc1f77ec9744c7faa42bce1a1c87416791449d6c87780
|
| 187 |
+
# via -r requirements/bootstrap.in
|
| 188 |
+
einops==0.8.2 \
|
| 189 |
+
--hash=sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193 \
|
| 190 |
+
--hash=sha256:609da665570e5e265e27283aab09e7f279ade90c4f01bcfca111f3d3e13f2827
|
| 191 |
+
# via -r requirements/bootstrap.in
|
| 192 |
+
filelock==3.32.2 \
|
| 193 |
+
--hash=sha256:87dd94cf281e586d135fa51132b8e3d9a598b316e90377a288663c9321036c82 \
|
| 194 |
+
--hash=sha256:c33351e1f49cae33414acbc6d56784e6ecee82514ec90795da1161fc4836b5b8
|
| 195 |
+
# via
|
| 196 |
+
# -r requirements/bootstrap.in
|
| 197 |
+
# huggingface-hub
|
| 198 |
+
# transformers
|
| 199 |
+
fonttools==4.63.0 \
|
| 200 |
+
--hash=sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69 \
|
| 201 |
+
--hash=sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c \
|
| 202 |
+
--hash=sha256:0c18358a155d75034911c5ee397a5b44cd19dd325dbb8b35fb60bf421d6a72ac \
|
| 203 |
+
--hash=sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096 \
|
| 204 |
+
--hash=sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d \
|
| 205 |
+
--hash=sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68 \
|
| 206 |
+
--hash=sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616 \
|
| 207 |
+
--hash=sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78 \
|
| 208 |
+
--hash=sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f \
|
| 209 |
+
--hash=sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b \
|
| 210 |
+
--hash=sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b \
|
| 211 |
+
--hash=sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02 \
|
| 212 |
+
--hash=sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d \
|
| 213 |
+
--hash=sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f \
|
| 214 |
+
--hash=sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8 \
|
| 215 |
+
--hash=sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272 \
|
| 216 |
+
--hash=sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49 \
|
| 217 |
+
--hash=sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419 \
|
| 218 |
+
--hash=sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001 \
|
| 219 |
+
--hash=sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03 \
|
| 220 |
+
--hash=sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196 \
|
| 221 |
+
--hash=sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9 \
|
| 222 |
+
--hash=sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e \
|
| 223 |
+
--hash=sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5 \
|
| 224 |
+
--hash=sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007 \
|
| 225 |
+
--hash=sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380 \
|
| 226 |
+
--hash=sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8 \
|
| 227 |
+
--hash=sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27 \
|
| 228 |
+
--hash=sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40 \
|
| 229 |
+
--hash=sha256:a8b33a82979e0a6a34ff435cc81317be1f95ec1ebb7a3a2d1c8a6a54f02ae44e \
|
| 230 |
+
--hash=sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0 \
|
| 231 |
+
--hash=sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263 \
|
| 232 |
+
--hash=sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb \
|
| 233 |
+
--hash=sha256:b1cd75a03ad8cb5bc40c90bfde68c0c47de423aa19e5c0f362b43520645eea94 \
|
| 234 |
+
--hash=sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b \
|
| 235 |
+
--hash=sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6 \
|
| 236 |
+
--hash=sha256:c0425b277a59cff3d80ca42162a8de360f318438a2ac83570842a678d826d579 \
|
| 237 |
+
--hash=sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4 \
|
| 238 |
+
--hash=sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59 \
|
| 239 |
+
--hash=sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0 \
|
| 240 |
+
--hash=sha256:cb014d58140a38135f16064c74c652ed57aa0b75cbf8bb59cac821f7edb5334e \
|
| 241 |
+
--hash=sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be \
|
| 242 |
+
--hash=sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd \
|
| 243 |
+
--hash=sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18 \
|
| 244 |
+
--hash=sha256:d7e5c9973aa04c95650c96e5f5ad865fbf42d62079163ecfab1e01cbc2504c22 \
|
| 245 |
+
--hash=sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0 \
|
| 246 |
+
--hash=sha256:e3297a6a4059b4acc3a1e9a8b04741f240a80044eef08ebd32e8b5bcdddce75b \
|
| 247 |
+
--hash=sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b \
|
| 248 |
+
--hash=sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af \
|
| 249 |
+
--hash=sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745
|
| 250 |
+
# via matplotlib
|
| 251 |
+
fsspec==2026.7.0 \
|
| 252 |
+
--hash=sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279 \
|
| 253 |
+
--hash=sha256:c803c40f4cf860b49dea58ee3e1c33cb9c790520e233537e1340049f89b82a88
|
| 254 |
+
# via
|
| 255 |
+
# -r requirements/bootstrap.in
|
| 256 |
+
# huggingface-hub
|
| 257 |
+
hatchling==1.27.0 \
|
| 258 |
+
--hash=sha256:971c296d9819abb3811112fc52c7a9751c8d381898f36533bb16f9791e941fd6 \
|
| 259 |
+
--hash=sha256:d3a2f3567c4f926ea39849cdf924c7e99e6686c9c8e288ae1037c8fa2a5d937b
|
| 260 |
+
# via -r requirements/bootstrap.in
|
| 261 |
+
hf-xet==1.5.2 \
|
| 262 |
+
--hash=sha256:045f84440c55cdeb659cf1a1dd48c77bcd0d2e93632e2fea8f2c3bdee79f38ed \
|
| 263 |
+
--hash=sha256:1da28519496eb7c8094c11e4d25509b4a468457a0302d58136099db2fd9a671d \
|
| 264 |
+
--hash=sha256:4a5ecb9cda8512ba2aa8ee5d37c87a1422992165892d653098c7b90247481c3b \
|
| 265 |
+
--hash=sha256:580e59e29bf37aece1f2b68537de1e3fb04f43a23d910dcf6f128280b5bfbba4 \
|
| 266 |
+
--hash=sha256:6395cfe3c9cbead4f16b31808b0e67eac428b66c656f856e99636adaddea878f \
|
| 267 |
+
--hash=sha256:73044bd31bae33c984af832d19c752a0dffb67518fee9ddbd91d616e1101cf47 \
|
| 268 |
+
--hash=sha256:7db73c810500c54c6760be8c39d4b2e476974de85424c50063efc22fdda13025 \
|
| 269 |
+
--hash=sha256:8764488197c1d7b1378c8438c18d2eea902e150dbca0b0f0d2d32603fb9b5576 \
|
| 270 |
+
--hash=sha256:8d7446f72abbf7e01ca5ff131786bc2e74a56393462c17a6bf1e303fbab81db4 \
|
| 271 |
+
--hash=sha256:bee28c619622d36968056532fd49cf2b35ca75099b1d616c31a618a893491380 \
|
| 272 |
+
--hash=sha256:cde8cd167126bb6109b2ceb19b844433a4988643e8f3e01dd9dd0e4a34535097 \
|
| 273 |
+
--hash=sha256:d6f9c58549407b84b9a5383afd68db0acc42345326a3159990b36a5ca8a20e4e \
|
| 274 |
+
--hash=sha256:db78c39c83d6279daddc98e2238f373ab8980685556d42472b4ec51abcf03e8c \
|
| 275 |
+
--hash=sha256:e396ab0faf6298199ad7a95305c3ca8498cb825978a6485be6d00587ee4ec577 \
|
| 276 |
+
--hash=sha256:ecf63d1cb69a9a7319910f8f83fcf9b46e7a32dfcf4b8f8eeddb55f647306e65 \
|
| 277 |
+
--hash=sha256:f922b8f5fb84f1dd3d7ab7a1316354a1bca9b1c73ecfc19c76e51a2a49d29799 \
|
| 278 |
+
--hash=sha256:fd3add255549e8ef58fa35b2e42dc016961c050600444e7d77d030ba6b57120e
|
| 279 |
+
# via huggingface-hub
|
| 280 |
+
huggingface-hub==0.36.2 \
|
| 281 |
+
--hash=sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a \
|
| 282 |
+
--hash=sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270
|
| 283 |
+
# via
|
| 284 |
+
# -r requirements/bootstrap.in
|
| 285 |
+
# tokenizers
|
| 286 |
+
# transformers
|
| 287 |
+
idna==3.18 \
|
| 288 |
+
--hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \
|
| 289 |
+
--hash=sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848
|
| 290 |
+
# via requests
|
| 291 |
+
jinja2==3.1.6 \
|
| 292 |
+
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
|
| 293 |
+
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
|
| 294 |
+
# via -r requirements/bootstrap.in
|
| 295 |
+
kiwisolver==1.5.0 \
|
| 296 |
+
--hash=sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9 \
|
| 297 |
+
--hash=sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679 \
|
| 298 |
+
--hash=sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0 \
|
| 299 |
+
--hash=sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8 \
|
| 300 |
+
--hash=sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276 \
|
| 301 |
+
--hash=sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96 \
|
| 302 |
+
--hash=sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e \
|
| 303 |
+
--hash=sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac \
|
| 304 |
+
--hash=sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f \
|
| 305 |
+
--hash=sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a \
|
| 306 |
+
--hash=sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15 \
|
| 307 |
+
--hash=sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7 \
|
| 308 |
+
--hash=sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368 \
|
| 309 |
+
--hash=sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02 \
|
| 310 |
+
--hash=sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9 \
|
| 311 |
+
--hash=sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681 \
|
| 312 |
+
--hash=sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57 \
|
| 313 |
+
--hash=sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27 \
|
| 314 |
+
--hash=sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4 \
|
| 315 |
+
--hash=sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920 \
|
| 316 |
+
--hash=sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374 \
|
| 317 |
+
--hash=sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3 \
|
| 318 |
+
--hash=sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa \
|
| 319 |
+
--hash=sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23 \
|
| 320 |
+
--hash=sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859 \
|
| 321 |
+
--hash=sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb \
|
| 322 |
+
--hash=sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d \
|
| 323 |
+
--hash=sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc \
|
| 324 |
+
--hash=sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581 \
|
| 325 |
+
--hash=sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c \
|
| 326 |
+
--hash=sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099 \
|
| 327 |
+
--hash=sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05 \
|
| 328 |
+
--hash=sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9 \
|
| 329 |
+
--hash=sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd \
|
| 330 |
+
--hash=sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc \
|
| 331 |
+
--hash=sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796 \
|
| 332 |
+
--hash=sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303 \
|
| 333 |
+
--hash=sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca \
|
| 334 |
+
--hash=sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314 \
|
| 335 |
+
--hash=sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489 \
|
| 336 |
+
--hash=sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57 \
|
| 337 |
+
--hash=sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1 \
|
| 338 |
+
--hash=sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797 \
|
| 339 |
+
--hash=sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021 \
|
| 340 |
+
--hash=sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db \
|
| 341 |
+
--hash=sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22 \
|
| 342 |
+
--hash=sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028 \
|
| 343 |
+
--hash=sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083 \
|
| 344 |
+
--hash=sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65 \
|
| 345 |
+
--hash=sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588 \
|
| 346 |
+
--hash=sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0 \
|
| 347 |
+
--hash=sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a \
|
| 348 |
+
--hash=sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1 \
|
| 349 |
+
--hash=sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c \
|
| 350 |
+
--hash=sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac \
|
| 351 |
+
--hash=sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476 \
|
| 352 |
+
--hash=sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53 \
|
| 353 |
+
--hash=sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3 \
|
| 354 |
+
--hash=sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4 \
|
| 355 |
+
--hash=sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615 \
|
| 356 |
+
--hash=sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb \
|
| 357 |
+
--hash=sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18 \
|
| 358 |
+
--hash=sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b \
|
| 359 |
+
--hash=sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1 \
|
| 360 |
+
--hash=sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2 \
|
| 361 |
+
--hash=sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c \
|
| 362 |
+
--hash=sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac \
|
| 363 |
+
--hash=sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d \
|
| 364 |
+
--hash=sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf \
|
| 365 |
+
--hash=sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2 \
|
| 366 |
+
--hash=sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f \
|
| 367 |
+
--hash=sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f \
|
| 368 |
+
--hash=sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4 \
|
| 369 |
+
--hash=sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9 \
|
| 370 |
+
--hash=sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e \
|
| 371 |
+
--hash=sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737 \
|
| 372 |
+
--hash=sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b \
|
| 373 |
+
--hash=sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed \
|
| 374 |
+
--hash=sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3 \
|
| 375 |
+
--hash=sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7 \
|
| 376 |
+
--hash=sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08 \
|
| 377 |
+
--hash=sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e \
|
| 378 |
+
--hash=sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902 \
|
| 379 |
+
--hash=sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd \
|
| 380 |
+
--hash=sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6 \
|
| 381 |
+
--hash=sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310 \
|
| 382 |
+
--hash=sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537 \
|
| 383 |
+
--hash=sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554 \
|
| 384 |
+
--hash=sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e \
|
| 385 |
+
--hash=sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87 \
|
| 386 |
+
--hash=sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a \
|
| 387 |
+
--hash=sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c \
|
| 388 |
+
--hash=sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79 \
|
| 389 |
+
--hash=sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e \
|
| 390 |
+
--hash=sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16 \
|
| 391 |
+
--hash=sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1 \
|
| 392 |
+
--hash=sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875 \
|
| 393 |
+
--hash=sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd \
|
| 394 |
+
--hash=sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0 \
|
| 395 |
+
--hash=sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9 \
|
| 396 |
+
--hash=sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646 \
|
| 397 |
+
--hash=sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657 \
|
| 398 |
+
--hash=sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4 \
|
| 399 |
+
--hash=sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232 \
|
| 400 |
+
--hash=sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819 \
|
| 401 |
+
--hash=sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384 \
|
| 402 |
+
--hash=sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309 \
|
| 403 |
+
--hash=sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede \
|
| 404 |
+
--hash=sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2 \
|
| 405 |
+
--hash=sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203 \
|
| 406 |
+
--hash=sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7 \
|
| 407 |
+
--hash=sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df \
|
| 408 |
+
--hash=sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c \
|
| 409 |
+
--hash=sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167 \
|
| 410 |
+
--hash=sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3 \
|
| 411 |
+
--hash=sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09 \
|
| 412 |
+
--hash=sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398
|
| 413 |
+
# via matplotlib
|
| 414 |
+
markupsafe==3.0.3 \
|
| 415 |
+
--hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \
|
| 416 |
+
--hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \
|
| 417 |
+
--hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \
|
| 418 |
+
--hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \
|
| 419 |
+
--hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \
|
| 420 |
+
--hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \
|
| 421 |
+
--hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \
|
| 422 |
+
--hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \
|
| 423 |
+
--hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \
|
| 424 |
+
--hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \
|
| 425 |
+
--hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \
|
| 426 |
+
--hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \
|
| 427 |
+
--hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \
|
| 428 |
+
--hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \
|
| 429 |
+
--hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \
|
| 430 |
+
--hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \
|
| 431 |
+
--hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \
|
| 432 |
+
--hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \
|
| 433 |
+
--hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \
|
| 434 |
+
--hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \
|
| 435 |
+
--hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \
|
| 436 |
+
--hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \
|
| 437 |
+
--hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \
|
| 438 |
+
--hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \
|
| 439 |
+
--hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \
|
| 440 |
+
--hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \
|
| 441 |
+
--hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \
|
| 442 |
+
--hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \
|
| 443 |
+
--hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \
|
| 444 |
+
--hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \
|
| 445 |
+
--hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \
|
| 446 |
+
--hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \
|
| 447 |
+
--hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \
|
| 448 |
+
--hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \
|
| 449 |
+
--hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \
|
| 450 |
+
--hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \
|
| 451 |
+
--hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \
|
| 452 |
+
--hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \
|
| 453 |
+
--hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \
|
| 454 |
+
--hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \
|
| 455 |
+
--hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \
|
| 456 |
+
--hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \
|
| 457 |
+
--hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \
|
| 458 |
+
--hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \
|
| 459 |
+
--hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \
|
| 460 |
+
--hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \
|
| 461 |
+
--hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \
|
| 462 |
+
--hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \
|
| 463 |
+
--hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \
|
| 464 |
+
--hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \
|
| 465 |
+
--hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \
|
| 466 |
+
--hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \
|
| 467 |
+
--hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \
|
| 468 |
+
--hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \
|
| 469 |
+
--hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \
|
| 470 |
+
--hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \
|
| 471 |
+
--hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \
|
| 472 |
+
--hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \
|
| 473 |
+
--hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \
|
| 474 |
+
--hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \
|
| 475 |
+
--hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \
|
| 476 |
+
--hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \
|
| 477 |
+
--hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \
|
| 478 |
+
--hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \
|
| 479 |
+
--hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \
|
| 480 |
+
--hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \
|
| 481 |
+
--hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \
|
| 482 |
+
--hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \
|
| 483 |
+
--hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \
|
| 484 |
+
--hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \
|
| 485 |
+
--hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \
|
| 486 |
+
--hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \
|
| 487 |
+
--hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \
|
| 488 |
+
--hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \
|
| 489 |
+
--hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \
|
| 490 |
+
--hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \
|
| 491 |
+
--hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \
|
| 492 |
+
--hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \
|
| 493 |
+
--hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \
|
| 494 |
+
--hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \
|
| 495 |
+
--hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \
|
| 496 |
+
--hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \
|
| 497 |
+
--hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \
|
| 498 |
+
--hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \
|
| 499 |
+
--hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \
|
| 500 |
+
--hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \
|
| 501 |
+
--hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \
|
| 502 |
+
--hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \
|
| 503 |
+
--hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50
|
| 504 |
+
# via jinja2
|
| 505 |
+
matplotlib==3.10.8 \
|
| 506 |
+
--hash=sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7 \
|
| 507 |
+
--hash=sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a \
|
| 508 |
+
--hash=sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f \
|
| 509 |
+
--hash=sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3 \
|
| 510 |
+
--hash=sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5 \
|
| 511 |
+
--hash=sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9 \
|
| 512 |
+
--hash=sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2 \
|
| 513 |
+
--hash=sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3 \
|
| 514 |
+
--hash=sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6 \
|
| 515 |
+
--hash=sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f \
|
| 516 |
+
--hash=sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b \
|
| 517 |
+
--hash=sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8 \
|
| 518 |
+
--hash=sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008 \
|
| 519 |
+
--hash=sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b \
|
| 520 |
+
--hash=sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656 \
|
| 521 |
+
--hash=sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958 \
|
| 522 |
+
--hash=sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04 \
|
| 523 |
+
--hash=sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b \
|
| 524 |
+
--hash=sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6 \
|
| 525 |
+
--hash=sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908 \
|
| 526 |
+
--hash=sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c \
|
| 527 |
+
--hash=sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1 \
|
| 528 |
+
--hash=sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d \
|
| 529 |
+
--hash=sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1 \
|
| 530 |
+
--hash=sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c \
|
| 531 |
+
--hash=sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a \
|
| 532 |
+
--hash=sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce \
|
| 533 |
+
--hash=sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a \
|
| 534 |
+
--hash=sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160 \
|
| 535 |
+
--hash=sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1 \
|
| 536 |
+
--hash=sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11 \
|
| 537 |
+
--hash=sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a \
|
| 538 |
+
--hash=sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466 \
|
| 539 |
+
--hash=sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486 \
|
| 540 |
+
--hash=sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78 \
|
| 541 |
+
--hash=sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17 \
|
| 542 |
+
--hash=sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077 \
|
| 543 |
+
--hash=sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565 \
|
| 544 |
+
--hash=sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f \
|
| 545 |
+
--hash=sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50 \
|
| 546 |
+
--hash=sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58 \
|
| 547 |
+
--hash=sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2 \
|
| 548 |
+
--hash=sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645 \
|
| 549 |
+
--hash=sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2 \
|
| 550 |
+
--hash=sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39 \
|
| 551 |
+
--hash=sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf \
|
| 552 |
+
--hash=sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149 \
|
| 553 |
+
--hash=sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22 \
|
| 554 |
+
--hash=sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df \
|
| 555 |
+
--hash=sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4 \
|
| 556 |
+
--hash=sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933 \
|
| 557 |
+
--hash=sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6 \
|
| 558 |
+
--hash=sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8 \
|
| 559 |
+
--hash=sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a \
|
| 560 |
+
--hash=sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7
|
| 561 |
+
# via -r requirements/bootstrap.in
|
| 562 |
+
mpmath==1.3.0 \
|
| 563 |
+
--hash=sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f \
|
| 564 |
+
--hash=sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c
|
| 565 |
+
# via sympy
|
| 566 |
+
networkx==3.6.1 \
|
| 567 |
+
--hash=sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509 \
|
| 568 |
+
--hash=sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762
|
| 569 |
+
# via -r requirements/bootstrap.in
|
| 570 |
+
numpy==1.26.4 \
|
| 571 |
+
--hash=sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b \
|
| 572 |
+
--hash=sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818 \
|
| 573 |
+
--hash=sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20 \
|
| 574 |
+
--hash=sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0 \
|
| 575 |
+
--hash=sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010 \
|
| 576 |
+
--hash=sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a \
|
| 577 |
+
--hash=sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea \
|
| 578 |
+
--hash=sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c \
|
| 579 |
+
--hash=sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71 \
|
| 580 |
+
--hash=sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110 \
|
| 581 |
+
--hash=sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be \
|
| 582 |
+
--hash=sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a \
|
| 583 |
+
--hash=sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a \
|
| 584 |
+
--hash=sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5 \
|
| 585 |
+
--hash=sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed \
|
| 586 |
+
--hash=sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd \
|
| 587 |
+
--hash=sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c \
|
| 588 |
+
--hash=sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e \
|
| 589 |
+
--hash=sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0 \
|
| 590 |
+
--hash=sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c \
|
| 591 |
+
--hash=sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a \
|
| 592 |
+
--hash=sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b \
|
| 593 |
+
--hash=sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0 \
|
| 594 |
+
--hash=sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6 \
|
| 595 |
+
--hash=sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2 \
|
| 596 |
+
--hash=sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a \
|
| 597 |
+
--hash=sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30 \
|
| 598 |
+
--hash=sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218 \
|
| 599 |
+
--hash=sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5 \
|
| 600 |
+
--hash=sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07 \
|
| 601 |
+
--hash=sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2 \
|
| 602 |
+
--hash=sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4 \
|
| 603 |
+
--hash=sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764 \
|
| 604 |
+
--hash=sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef \
|
| 605 |
+
--hash=sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3 \
|
| 606 |
+
--hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f
|
| 607 |
+
# via
|
| 608 |
+
# -r requirements/bootstrap.in
|
| 609 |
+
# contourpy
|
| 610 |
+
# matplotlib
|
| 611 |
+
# transformers
|
| 612 |
+
packaging==26.2 \
|
| 613 |
+
--hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \
|
| 614 |
+
--hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661
|
| 615 |
+
# via
|
| 616 |
+
# hatchling
|
| 617 |
+
# huggingface-hub
|
| 618 |
+
# matplotlib
|
| 619 |
+
# transformers
|
| 620 |
+
pathspec==1.1.1 \
|
| 621 |
+
--hash=sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a \
|
| 622 |
+
--hash=sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189
|
| 623 |
+
# via hatchling
|
| 624 |
+
pillow==12.1.1 \
|
| 625 |
+
--hash=sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9 \
|
| 626 |
+
--hash=sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da \
|
| 627 |
+
--hash=sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f \
|
| 628 |
+
--hash=sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642 \
|
| 629 |
+
--hash=sha256:178aa072084bd88ec759052feca8e56cbb14a60b39322b99a049e58090479713 \
|
| 630 |
+
--hash=sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850 \
|
| 631 |
+
--hash=sha256:1a9b0ee305220b392e1124a764ee4265bd063e54a751a6b62eff69992f457fa9 \
|
| 632 |
+
--hash=sha256:1f1625b72740fdda5d77b4def688eb8fd6490975d06b909fd19f13f391e077e0 \
|
| 633 |
+
--hash=sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9 \
|
| 634 |
+
--hash=sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8 \
|
| 635 |
+
--hash=sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6 \
|
| 636 |
+
--hash=sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd \
|
| 637 |
+
--hash=sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5 \
|
| 638 |
+
--hash=sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c \
|
| 639 |
+
--hash=sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35 \
|
| 640 |
+
--hash=sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1 \
|
| 641 |
+
--hash=sha256:36341d06738a9f66c8287cf8b876d24b18db9bd8740fa0672c74e259ad408cff \
|
| 642 |
+
--hash=sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38 \
|
| 643 |
+
--hash=sha256:3a5cbdcddad0af3da87cb16b60d23648bc3b51967eb07223e9fed77a82b457c4 \
|
| 644 |
+
--hash=sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af \
|
| 645 |
+
--hash=sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60 \
|
| 646 |
+
--hash=sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986 \
|
| 647 |
+
--hash=sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13 \
|
| 648 |
+
--hash=sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717 \
|
| 649 |
+
--hash=sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e \
|
| 650 |
+
--hash=sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b \
|
| 651 |
+
--hash=sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15 \
|
| 652 |
+
--hash=sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a \
|
| 653 |
+
--hash=sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb \
|
| 654 |
+
--hash=sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d \
|
| 655 |
+
--hash=sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b \
|
| 656 |
+
--hash=sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e \
|
| 657 |
+
--hash=sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a \
|
| 658 |
+
--hash=sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f \
|
| 659 |
+
--hash=sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a \
|
| 660 |
+
--hash=sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce \
|
| 661 |
+
--hash=sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc \
|
| 662 |
+
--hash=sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f \
|
| 663 |
+
--hash=sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586 \
|
| 664 |
+
--hash=sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f \
|
| 665 |
+
--hash=sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9 \
|
| 666 |
+
--hash=sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8 \
|
| 667 |
+
--hash=sha256:6c52f062424c523d6c4db85518774cc3d50f5539dd6eed32b8f6229b26f24d40 \
|
| 668 |
+
--hash=sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60 \
|
| 669 |
+
--hash=sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c \
|
| 670 |
+
--hash=sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0 \
|
| 671 |
+
--hash=sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334 \
|
| 672 |
+
--hash=sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af \
|
| 673 |
+
--hash=sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735 \
|
| 674 |
+
--hash=sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524 \
|
| 675 |
+
--hash=sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf \
|
| 676 |
+
--hash=sha256:89c7e895002bbe49cdc5426150377cbbc04767d7547ed145473f496dfa40408b \
|
| 677 |
+
--hash=sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2 \
|
| 678 |
+
--hash=sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9 \
|
| 679 |
+
--hash=sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7 \
|
| 680 |
+
--hash=sha256:99c1506ea77c11531d75e3a412832a13a71c7ebc8192ab9e4b2e355555920e3e \
|
| 681 |
+
--hash=sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4 \
|
| 682 |
+
--hash=sha256:9f51079765661884a486727f0729d29054242f74b46186026582b4e4769918e4 \
|
| 683 |
+
--hash=sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b \
|
| 684 |
+
--hash=sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397 \
|
| 685 |
+
--hash=sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c \
|
| 686 |
+
--hash=sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e \
|
| 687 |
+
--hash=sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029 \
|
| 688 |
+
--hash=sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3 \
|
| 689 |
+
--hash=sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052 \
|
| 690 |
+
--hash=sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984 \
|
| 691 |
+
--hash=sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293 \
|
| 692 |
+
--hash=sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523 \
|
| 693 |
+
--hash=sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f \
|
| 694 |
+
--hash=sha256:b66e95d05ba806247aaa1561f080abc7975daf715c30780ff92a20e4ec546e1b \
|
| 695 |
+
--hash=sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80 \
|
| 696 |
+
--hash=sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f \
|
| 697 |
+
--hash=sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79 \
|
| 698 |
+
--hash=sha256:c6008de247150668a705a6338156efb92334113421ceecf7438a12c9a12dab23 \
|
| 699 |
+
--hash=sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8 \
|
| 700 |
+
--hash=sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e \
|
| 701 |
+
--hash=sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3 \
|
| 702 |
+
--hash=sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e \
|
| 703 |
+
--hash=sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36 \
|
| 704 |
+
--hash=sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f \
|
| 705 |
+
--hash=sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5 \
|
| 706 |
+
--hash=sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f \
|
| 707 |
+
--hash=sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6 \
|
| 708 |
+
--hash=sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32 \
|
| 709 |
+
--hash=sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20 \
|
| 710 |
+
--hash=sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202 \
|
| 711 |
+
--hash=sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0 \
|
| 712 |
+
--hash=sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3 \
|
| 713 |
+
--hash=sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563 \
|
| 714 |
+
--hash=sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090 \
|
| 715 |
+
--hash=sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289
|
| 716 |
+
# via
|
| 717 |
+
# -r requirements/bootstrap.in
|
| 718 |
+
# matplotlib
|
| 719 |
+
pluggy==1.6.0 \
|
| 720 |
+
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
|
| 721 |
+
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
|
| 722 |
+
# via hatchling
|
| 723 |
+
psutil==7.2.2 \
|
| 724 |
+
--hash=sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372 \
|
| 725 |
+
--hash=sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9 \
|
| 726 |
+
--hash=sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841 \
|
| 727 |
+
--hash=sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63 \
|
| 728 |
+
--hash=sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979 \
|
| 729 |
+
--hash=sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a \
|
| 730 |
+
--hash=sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b \
|
| 731 |
+
--hash=sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9 \
|
| 732 |
+
--hash=sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee \
|
| 733 |
+
--hash=sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312 \
|
| 734 |
+
--hash=sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b \
|
| 735 |
+
--hash=sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9 \
|
| 736 |
+
--hash=sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e \
|
| 737 |
+
--hash=sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc \
|
| 738 |
+
--hash=sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1 \
|
| 739 |
+
--hash=sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf \
|
| 740 |
+
--hash=sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea \
|
| 741 |
+
--hash=sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988 \
|
| 742 |
+
--hash=sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486 \
|
| 743 |
+
--hash=sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00 \
|
| 744 |
+
--hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8
|
| 745 |
+
# via -r requirements/bootstrap.in
|
| 746 |
+
pyparsing==3.3.2 \
|
| 747 |
+
--hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d \
|
| 748 |
+
--hash=sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc
|
| 749 |
+
# via matplotlib
|
| 750 |
+
pypdfium2==5.12.1 \
|
| 751 |
+
--hash=sha256:05bab9b1ba2de7fc299ae2af25cb9c8a0543bc8bb893e879fe8c9ba8310e9ce4 \
|
| 752 |
+
--hash=sha256:05bfa20a08a96584253bbe38b60e13f81a037eac31c5579e607ec1480ad25dbf \
|
| 753 |
+
--hash=sha256:07eeebb2784f4cd38d386b924235df43217a397442796673296bb6efbdaad1d0 \
|
| 754 |
+
--hash=sha256:236dbdc88aa54f14b27937ccb2ebe3dcf08c10dbb8652f432ea982dc9af39732 \
|
| 755 |
+
--hash=sha256:4648f0905441bcb141687ca2263bbf38a1aa056b943eef06019f91cff3e1da4a \
|
| 756 |
+
--hash=sha256:5c3e6cbe43581af79526184643920ab03a9401a0c79f2226bea9d4d1e3d34008 \
|
| 757 |
+
--hash=sha256:5f257bb40fa44ce9ba18d2c919777dbd3f16bf22548b1d68fd56c7c92f1de530 \
|
| 758 |
+
--hash=sha256:66a9ed40d70a5d728cd42148fecb9d7a0917c6161d6bb67c844093a4ed1df089 \
|
| 759 |
+
--hash=sha256:6eabf028ad8e7bc7811c9acf3a72718c180569b624b844d2c6cc974609784275 \
|
| 760 |
+
--hash=sha256:715ae16b34ea1d64884d58800155179ba700e9ea65a2f583b020666acd2bfb12 \
|
| 761 |
+
--hash=sha256:7857cfa6642ec5a09db12ff8f5cf6b6494585b5e3a605399fddc4fb862837b63 \
|
| 762 |
+
--hash=sha256:847378a5ab41332998b2621b21bab2e96dc8c3eff36a08bce26695b964163983 \
|
| 763 |
+
--hash=sha256:9609be73a6701a68f29dffe0335f7a2e4b3ba581542ed65d35d49f761a4600ca \
|
| 764 |
+
--hash=sha256:974082344172da76a5c3c0782eaedfe6069dbe88db77d8c671ef36b61e9b14e2 \
|
| 765 |
+
--hash=sha256:9c8856ce7dd77a7827476c7d75afe1197d6cd505f5cb4167b6aacf661f3f8ea5 \
|
| 766 |
+
--hash=sha256:9f059f7bdbdf4352eb83691071096940d769d6ae5930b8734237fdb1bd78fbc2 \
|
| 767 |
+
--hash=sha256:afc0b7e0c975a429abc75875209ce17b66d749f6ac5cbe8ba72470e83901e304 \
|
| 768 |
+
--hash=sha256:bdff622181fab64f32328591c9c8287cdc745c9a1f2afc26ca3feba39e3e6645 \
|
| 769 |
+
--hash=sha256:d0e0648fb2e28f50efcd1ec0a5a18ced9f4d66b2c227fae9b603f0a883b2d13f \
|
| 770 |
+
--hash=sha256:d4ee061e566a6422b660cdddaaa799a2d1cbf2f016921bcaf24d61426d01d942 \
|
| 771 |
+
--hash=sha256:e10cbf41b21233ec5e20adfc170cf60edd77abead86a97dc708fff55a8a886c7 \
|
| 772 |
+
--hash=sha256:e5358d2ce4ebc5c899aab1df9ca5d215357244e9168aa443225d3c1e649c7eac
|
| 773 |
+
# via -r requirements/bootstrap.in
|
| 774 |
+
python-dateutil==2.9.0.post0 \
|
| 775 |
+
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
|
| 776 |
+
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
|
| 777 |
+
# via matplotlib
|
| 778 |
+
pyyaml==6.0.3 \
|
| 779 |
+
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \
|
| 780 |
+
--hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \
|
| 781 |
+
--hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \
|
| 782 |
+
--hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 \
|
| 783 |
+
--hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \
|
| 784 |
+
--hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c \
|
| 785 |
+
--hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 \
|
| 786 |
+
--hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a \
|
| 787 |
+
--hash=sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0 \
|
| 788 |
+
--hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b \
|
| 789 |
+
--hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \
|
| 790 |
+
--hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 \
|
| 791 |
+
--hash=sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7 \
|
| 792 |
+
--hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e \
|
| 793 |
+
--hash=sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007 \
|
| 794 |
+
--hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 \
|
| 795 |
+
--hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 \
|
| 796 |
+
--hash=sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9 \
|
| 797 |
+
--hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 \
|
| 798 |
+
--hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \
|
| 799 |
+
--hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 \
|
| 800 |
+
--hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \
|
| 801 |
+
--hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac \
|
| 802 |
+
--hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 \
|
| 803 |
+
--hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 \
|
| 804 |
+
--hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 \
|
| 805 |
+
--hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \
|
| 806 |
+
--hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b \
|
| 807 |
+
--hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 \
|
| 808 |
+
--hash=sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5 \
|
| 809 |
+
--hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \
|
| 810 |
+
--hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \
|
| 811 |
+
--hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 \
|
| 812 |
+
--hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \
|
| 813 |
+
--hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \
|
| 814 |
+
--hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 \
|
| 815 |
+
--hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 \
|
| 816 |
+
--hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c \
|
| 817 |
+
--hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \
|
| 818 |
+
--hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 \
|
| 819 |
+
--hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \
|
| 820 |
+
--hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b \
|
| 821 |
+
--hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 \
|
| 822 |
+
--hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac \
|
| 823 |
+
--hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \
|
| 824 |
+
--hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \
|
| 825 |
+
--hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \
|
| 826 |
+
--hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 \
|
| 827 |
+
--hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \
|
| 828 |
+
--hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 \
|
| 829 |
+
--hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b \
|
| 830 |
+
--hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \
|
| 831 |
+
--hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \
|
| 832 |
+
--hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 \
|
| 833 |
+
--hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 \
|
| 834 |
+
--hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 \
|
| 835 |
+
--hash=sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da \
|
| 836 |
+
--hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \
|
| 837 |
+
--hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \
|
| 838 |
+
--hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c \
|
| 839 |
+
--hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba \
|
| 840 |
+
--hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f \
|
| 841 |
+
--hash=sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917 \
|
| 842 |
+
--hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \
|
| 843 |
+
--hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \
|
| 844 |
+
--hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \
|
| 845 |
+
--hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b \
|
| 846 |
+
--hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \
|
| 847 |
+
--hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \
|
| 848 |
+
--hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 \
|
| 849 |
+
--hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \
|
| 850 |
+
--hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \
|
| 851 |
+
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
|
| 852 |
+
# via
|
| 853 |
+
# huggingface-hub
|
| 854 |
+
# transformers
|
| 855 |
+
regex==2026.7.19 \
|
| 856 |
+
--hash=sha256:062f8cb7a9739c4835d22bd96f370c59aba89f257adcfa53be3cc209e08d3ae0 \
|
| 857 |
+
--hash=sha256:064f1760a5a4ade65c5419be23e782f29147528e8a66e0c42dd4cedb8d4e9fc6 \
|
| 858 |
+
--hash=sha256:09523a592938aa9f587fb74467c63ff0cf88fc3df14c82ab0f0517dcf76aaa62 \
|
| 859 |
+
--hash=sha256:09d3007fc76249a83cdd33de160d50e6cb77f54e09d8fa9e7148e10607ce24af \
|
| 860 |
+
--hash=sha256:09f3e5287f94f17b709dc9a9e70865855feee835c861613be144218ce4ca82cc \
|
| 861 |
+
--hash=sha256:0c41c63992bf1874cebb6e7f56fd7d3c007924659a604ae3d90e427d40d4fd13 \
|
| 862 |
+
--hash=sha256:0e9554c8785eac5cffe6300f69a91f58ba72bc88a5f8d661235ad7c6aa5b8ccd \
|
| 863 |
+
--hash=sha256:1123ef4211d763ee771d47916a1596e2f4915794f7aabdc1adcb20e4249a6951 \
|
| 864 |
+
--hash=sha256:15b364b9b98d6d2fe1a85034c23a3180ff913f46caddc3895f6fd65186255ccc \
|
| 865 |
+
--hash=sha256:1649eb39fcc9ea80c4d2f110fde2b8ab2aef3877b98f02ab9b14e961f418c511 \
|
| 866 |
+
--hash=sha256:17ed5692f6acc4183e98331101a5f9e4f64d72fe58b753da4d444a2c77d05b12 \
|
| 867 |
+
--hash=sha256:199535629f25caf89698039af3d1ad5fcae7f933e2112c73f1cdf49165c99518 \
|
| 868 |
+
--hash=sha256:1c398716054621aa300b3d411f467dda903806c5da0df6945ab73982b8d115db \
|
| 869 |
+
--hash=sha256:1d3372064506b94dd2c67c845f2db8062e9e9ba84d04e33cb96d7d33c11fe1ae \
|
| 870 |
+
--hash=sha256:1d58561843f0ff7dc78b4c28b5e2dc388f3eff94ebc8a232a3adba961fc00009 \
|
| 871 |
+
--hash=sha256:1d793a7988e04fcb1e2e135567443d82173225d657419ec09414a9b5a145b986 \
|
| 872 |
+
--hash=sha256:1ebac3474b8589fce2f9b225b650afd61448f7c73a5d0255a10cc6366471aed1 \
|
| 873 |
+
--hash=sha256:20568e182eb82d39a6bf7cff3fd58566f14c75c6f74b2c8c96537eecf9010e3a \
|
| 874 |
+
--hash=sha256:22a992de9a0d91bda927bf02b94351d737a0302905432c88a53de7c4b9ce62e2 \
|
| 875 |
+
--hash=sha256:2955907b7157a6660f27079edf7e0229e9c9c5325c77a2ef6a890cba91efa6f0 \
|
| 876 |
+
--hash=sha256:2c4e61e2e1be56f63ec3cc618aa9e0de81ef6f43d177205451840022e24f5b78 \
|
| 877 |
+
--hash=sha256:2cc3460cedf7579948486eab03bc9ad7089df4d7281c0f47f4afe03e8d13f02d \
|
| 878 |
+
--hash=sha256:2ce9e679f776649746729b6c86382da519ef649c8e34cc41df0d2e5e0f6c36d4 \
|
| 879 |
+
--hash=sha256:2ef7eeb108c47ce7bcc9513e51bcb1bf57e8f483d52fce68a8642e3527141ae0 \
|
| 880 |
+
--hash=sha256:3080a7fd38ef049bd489e01c970c97dd84ff446a885b0f1f6b26d9b1ad13ce11 \
|
| 881 |
+
--hash=sha256:343a4504e3fb688c47cad451221ca5d4814f42b1e16c0065bde9cbf7f473bd52 \
|
| 882 |
+
--hash=sha256:36aacfb15faaff3ced55afbf35ec72f50d4aee22082c4f7fe0573a33e2fca92e \
|
| 883 |
+
--hash=sha256:3d3143f159261b1ce5b24c261c590e5913370c3200c5e9ebbb92b5aa5e111902 \
|
| 884 |
+
--hash=sha256:40b34dd88658e4fedd2fddbf0275ac970d00614b731357f425722a3ed1983d11 \
|
| 885 |
+
--hash=sha256:4458124d71339f505bf1fb94f69fd1bb8fa9d2481eebfef27c10ef4f2b9e12f6 \
|
| 886 |
+
--hash=sha256:4896db1f4ce0576765b8272aa922df324e0f5b9bb2c3d03044ff32a7234a9aba \
|
| 887 |
+
--hash=sha256:4a0530bb1b8c1c985e7e2122e2b4d3aedd8a3c21c6bfddae6767c4405668b56e \
|
| 888 |
+
--hash=sha256:4aa5435cdb3eb6f55fe98a171b05e3fbcd95fadaa4aa32acf62afd9b0cfdbcac \
|
| 889 |
+
--hash=sha256:4c3501bfa814ab07b5580741f9bf78dfdfe146a04057f82df9e2402d2a975939 \
|
| 890 |
+
--hash=sha256:4e5413bd5f13d3a4e3539ca98f70f75e7fca92518dd7f117f030ebedd10b60cb \
|
| 891 |
+
--hash=sha256:4e6883a021db30511d9fb8cfb0f222ce1f2c369f7d4d8b0448f449a93ba0bdfc \
|
| 892 |
+
--hash=sha256:52579c60a6078be70a0e49c81d6e56d677f34cd439af281a0083b8c7bc75c095 \
|
| 893 |
+
--hash=sha256:555497390743af1a65045fa4527782d10ff5b88970359412baa4a1e628fe393b \
|
| 894 |
+
--hash=sha256:56ad4d9f77df871a99e25c37091052a02528ec0eb059de928ee33956b854b45b \
|
| 895 |
+
--hash=sha256:571fde9741eb0ccde23dd4e0c1d50fbae910e901fa7e629faf39b2dda740d220 \
|
| 896 |
+
--hash=sha256:572fc57b0009c735ee56c175ea021b637a15551a312f56734277f923d6fd0f6c \
|
| 897 |
+
--hash=sha256:59787bd5f8c70aa339084e961d2996b53fbdeab4d5393bba5c1fe1fc32e02bae \
|
| 898 |
+
--hash=sha256:5a2721c8720e2cb3c209925dfb9200199b4b07361c9e01d321719404b21458b3 \
|
| 899 |
+
--hash=sha256:5cc26a66e212fa5d6c6170c3a40d99d888db3020c6fdab1523250d4341382e44 \
|
| 900 |
+
--hash=sha256:5ebee1ee89c39c953baac6924fcde08c5bb427c4057510862f9d7c7bdb3d8665 \
|
| 901 |
+
--hash=sha256:60be8693a1dadc210bbcbc0db3e26da5f7d01d1d5a3da594e99b4fa42df404f5 \
|
| 902 |
+
--hash=sha256:618a0aed532be87294c4477b0481f3aa0f1520f4014a4374dd4cf789b4cd2c97 \
|
| 903 |
+
--hash=sha256:61bb1bd45520aacd56dd80943bd34991fb5350afdd1f36f2282230fd5154a218 \
|
| 904 |
+
--hash=sha256:6383cd2ed53a646c659ba1fe65727db76437fdaa069e697a0b44a51d5843d864 \
|
| 905 |
+
--hash=sha256:64729333167c2dcaaa56a331d40ee097bd9c5617ffd51dabb09eaddafb1b532e \
|
| 906 |
+
--hash=sha256:64b6ca7391a1395c2638dd5c7456d67bea44fc6c5e8e92c5dc8aa6a8f23292b4 \
|
| 907 |
+
--hash=sha256:65dcd28d3eba2ab7c2fd906485cc301392b47cc2234790d27d4e4814e02cdfda \
|
| 908 |
+
--hash=sha256:65fa6cb38ed5e9c3637e68e544f598b39c3b86b808ed0627a67b68320384b459 \
|
| 909 |
+
--hash=sha256:66bd62c59a5427746e8c44becae1d9b99d22fb13f30f492083dfb9ad7c45cc18 \
|
| 910 |
+
--hash=sha256:6e44c0e7c5664be20aee92085153150c0a7967310a73a43c0f832b7cd35d0dd3 \
|
| 911 |
+
--hash=sha256:6f8c6e7a1cfa3dc9d0ee2de0e65e834537fa29992cc3976ffec914afc35c5dd5 \
|
| 912 |
+
--hash=sha256:7322ec6cc9fba9d49ab888bb82d67ac5625627aa168f0165139b17018df3fb8a \
|
| 913 |
+
--hash=sha256:73b133a9e6fb512858e7f065e96f1180aa46646bc74a83aea62f1d314f3dd035 \
|
| 914 |
+
--hash=sha256:73f272fba87b8ccfe70a137d02a54af386f6d27aa509fbffdd978f5947aae1aa \
|
| 915 |
+
--hash=sha256:7e77b324909c1617cbb4c668677e2c6ae13f44d7c1de0d4f15f2e3c10f3315b5 \
|
| 916 |
+
--hash=sha256:80115dd39481fd3a4b4080220799dbcacb921a844de4b827264ececacbe17c78 \
|
| 917 |
+
--hash=sha256:87ccab0db8d5f4fbb0272642113c1adb2ffc698c16d3a0944580222331fa7a20 \
|
| 918 |
+
--hash=sha256:89dfee3319f5ae3f75ebd5c2445a809bb320252ba5529ffdafea4ef25d79cf1a \
|
| 919 |
+
--hash=sha256:8ac59a0900474a52b7c04af8196affc22bd9842acb0950df12f7b813e983609a \
|
| 920 |
+
--hash=sha256:8cae6fd77a5b72dae505084b1a2ee0360139faf72fedbab667cd7cc65aae7a6a \
|
| 921 |
+
--hash=sha256:8d3469c91dd92ee41b7c95280edbd975ef1ba9195086686623a1c6e8935ce965 \
|
| 922 |
+
--hash=sha256:90c633e7e8d6bf4e992b8b36ce69e018f834b641dd6de8cea6d78c06ffa119c5 \
|
| 923 |
+
--hash=sha256:93db40c8de0815baab96a06e08a984bac71f989d13bab789e382158c5d426797 \
|
| 924 |
+
--hash=sha256:9724e6cb5e478cd7d8cabf027826178739cb18cf0e117d0e32814d479fa02276 \
|
| 925 |
+
--hash=sha256:98c6ac18480fcdb33f35439183f1d2e79760ab41930309c6d951cb1f8e46694c \
|
| 926 |
+
--hash=sha256:9a15e785f244f3e07847b984ce8773fc3da10a9f3c131cc49a4c5b4d672b4547 \
|
| 927 |
+
--hash=sha256:9b60d7814174f059e5de4ab98271cc5ba9259cfea55273a81544dceea32dc8d9 \
|
| 928 |
+
--hash=sha256:9be2a6647740dd3cca6acb24e87f03d7632cd280dbce9bbe40c26353a215a45d \
|
| 929 |
+
--hash=sha256:9c7472192ebfad53a6be7c4a8bfb2d64b81c0e93a1fc8c57e1dd0b638297b5d1 \
|
| 930 |
+
--hash=sha256:9dce8ec9695f531a1b8a6f314fd4b393adcccf2ea861db480cdf97a301d01a68 \
|
| 931 |
+
--hash=sha256:9e50d748a32da622f256e8d505867f5d3c43a837c6a9f0efb149655fadd1042a \
|
| 932 |
+
--hash=sha256:a81758ed242b861b72e778ba34d41366441a2e10b16b472784c88da2dea7e2dd \
|
| 933 |
+
--hash=sha256:ac777001cdfc28b72477d93c8564bb7583081ea8fb45cdca3d568e0a4f87183c \
|
| 934 |
+
--hash=sha256:b2b506b1788df5fecd270a10d5e70a95fe77b87ea2b370a318043f6f5f817ee6 \
|
| 935 |
+
--hash=sha256:b2ea4a3e8357be8849e833beeae757ac3c7a6b3fc055c03c808a53c91ad30d82 \
|
| 936 |
+
--hash=sha256:bf1516fe58fc104f39b2d1dbe2d5e27d0cd45c4be2e42ba6ee0cc763701ec3c7 \
|
| 937 |
+
--hash=sha256:c0d702548d89d572b2929879bc883bb7a4c4709efafe4512cadee56c55c9bd15 \
|
| 938 |
+
--hash=sha256:c10b82c2634df08dfb13b1f04e38fe310d086ee092f4f69c0c8da234251e556e \
|
| 939 |
+
--hash=sha256:c42572142ed0b9d5d261ba727157c426510da78e20828b66bbb855098b8a4e38 \
|
| 940 |
+
--hash=sha256:c4585c3e64b4f9e583b4d2683f18f5d5d872b3d71dcf24594b74ecc23602fa96 \
|
| 941 |
+
--hash=sha256:c639ea314df70a7b2811e8020448c75af8c9445f5a60f8a4ced81c306a9380c2 \
|
| 942 |
+
--hash=sha256:c670fe7be5b6020b76bc6e8d2196074657e1327595bca93a389e1a76ab130ad8 \
|
| 943 |
+
--hash=sha256:cc1b2440423a851fad781309dd87843868f4f66a6bcd1ddb9225cf4ec2c84732 \
|
| 944 |
+
--hash=sha256:cd3584591ea4429026cdb931b054342c2bcf189b44ff367f8d5c15bc092a2966 \
|
| 945 |
+
--hash=sha256:d15df07081d91b76ff20d43f94592ee110330152d617b730fdbe5ef9fb680053 \
|
| 946 |
+
--hash=sha256:d19662dbedbe783d323196312d38f5ba53cf56296378252171985da6899887d3 \
|
| 947 |
+
--hash=sha256:d24ecb4f5e009ea0bd275ee37ad9953b32005e2e5e60f8bbae16da0dbbf0d3a0 \
|
| 948 |
+
--hash=sha256:d446c6ac40bb6e05025ccee55b84d80fe9bf8e93010ffc4bb9484f13d498835f \
|
| 949 |
+
--hash=sha256:d51ffd3427640fa2da6ade574ceba932f210ad095f65fcc450a2b0a0d454868e \
|
| 950 |
+
--hash=sha256:d6ce43a0269d68cee79a7d1ade7def53c20f8f2a047b92d7b5d5bcc73ae88327 \
|
| 951 |
+
--hash=sha256:d721e53758b2cca74990185eb0671dd466d7a388a1a45d0c6f4c13cef41a68ac \
|
| 952 |
+
--hash=sha256:d7da47a0f248977f08e2cb659ff3c17ddc13a4d39b3a7baa0a81bf5b415430f6 \
|
| 953 |
+
--hash=sha256:db47b561c9afd884baa1f96f797c9ca369872c4b65912bc691cfa99e68340af2 \
|
| 954 |
+
--hash=sha256:dbe6493fbd27321b1d1f2dd4f5c7e5bd4d8b1d7cab7f32fd67db3d0b2ed8248a \
|
| 955 |
+
--hash=sha256:dbece16025afda5e3031af0c4059207e61dcf73ef13af844964f57f387d1c435 \
|
| 956 |
+
--hash=sha256:ddd67571c10869f65a5d7dde536d1e066e306cc90de57d7de4d5f34802428bb5 \
|
| 957 |
+
--hash=sha256:de9208bb427130c82a5dbfd104f92c8876fc9559278c880b3002755bbbe9c83d \
|
| 958 |
+
--hash=sha256:e30d40268a28d54ce0437031750497004c22602b8e3ab891f759b795a003b312 \
|
| 959 |
+
--hash=sha256:e8b0abe7d870f53ca5143895fef7d1041a0c831a140d3dc2c760dd7ba25d4a8b \
|
| 960 |
+
--hash=sha256:f035d9dc1d25eff9d361456572231c7d27b5ccd473ca7dc0adfce732bd006d40 \
|
| 961 |
+
--hash=sha256:f04b9f56b0e0614c0126be12c2c2d9f8850c1e57af302bd0a63bed379d4af974 \
|
| 962 |
+
--hash=sha256:f0fa4fa9c3632d708742baf2282f2055c11d888a790362670a403cbf48a2c404 \
|
| 963 |
+
--hash=sha256:f2e7f8e2ab6c2922be02c7ec45185aa5bd771e2e57b95455ee343a44d8130dff \
|
| 964 |
+
--hash=sha256:f8f6fa298bb4f7f58a33334406218ba74716e68feddf5e4e54cd5d8082705abf \
|
| 965 |
+
--hash=sha256:fbf300e2070bb35038660b3be1be4b91b0024edb41517e6996320b49b92b4175 \
|
| 966 |
+
--hash=sha256:fce7760bf283405b2c7999cab3da4e72f7deca6396013115e3f7a955db9760da \
|
| 967 |
+
--hash=sha256:fcee38cd8e5089d6d4f048ba1233b3ad76e5954f545382180889112ff5cb712d \
|
| 968 |
+
--hash=sha256:fe31f28c94402043161876a258a9c6f757cb485905c7614ce8d6cd40e6b7bdc1 \
|
| 969 |
+
--hash=sha256:ffd8893ccc1c2fce6e0d6ca402d716fe1b29db70c7132609a05955e31b2aa8f2
|
| 970 |
+
# via transformers
|
| 971 |
+
requests==2.34.2 \
|
| 972 |
+
--hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \
|
| 973 |
+
--hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed
|
| 974 |
+
# via
|
| 975 |
+
# -r requirements/bootstrap.in
|
| 976 |
+
# huggingface-hub
|
| 977 |
+
# transformers
|
| 978 |
+
safetensors==0.8.0 \
|
| 979 |
+
--hash=sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358 \
|
| 980 |
+
--hash=sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f \
|
| 981 |
+
--hash=sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d \
|
| 982 |
+
--hash=sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d \
|
| 983 |
+
--hash=sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0 \
|
| 984 |
+
--hash=sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc \
|
| 985 |
+
--hash=sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235 \
|
| 986 |
+
--hash=sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98 \
|
| 987 |
+
--hash=sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4 \
|
| 988 |
+
--hash=sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846 \
|
| 989 |
+
--hash=sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca \
|
| 990 |
+
--hash=sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0 \
|
| 991 |
+
--hash=sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25 \
|
| 992 |
+
--hash=sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452 \
|
| 993 |
+
--hash=sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d \
|
| 994 |
+
--hash=sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78 \
|
| 995 |
+
--hash=sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774
|
| 996 |
+
# via
|
| 997 |
+
# -r requirements/bootstrap.in
|
| 998 |
+
# transformers
|
| 999 |
+
setuptools==83.0.0 \
|
| 1000 |
+
--hash=sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef \
|
| 1001 |
+
--hash=sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3
|
| 1002 |
+
# via -r requirements/bootstrap.in
|
| 1003 |
+
six==1.17.0 \
|
| 1004 |
+
--hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
|
| 1005 |
+
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
|
| 1006 |
+
# via python-dateutil
|
| 1007 |
+
sympy==1.14.0 \
|
| 1008 |
+
--hash=sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517 \
|
| 1009 |
+
--hash=sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5
|
| 1010 |
+
# via -r requirements/bootstrap.in
|
| 1011 |
+
tokenizers==0.22.2 \
|
| 1012 |
+
--hash=sha256:143b999bdc46d10febb15cbffb4207ddd1f410e2c755857b5a0797961bbdc113 \
|
| 1013 |
+
--hash=sha256:1a62ba2c5faa2dd175aaeed7b15abf18d20266189fb3406c5d0550dd34dd5f37 \
|
| 1014 |
+
--hash=sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e \
|
| 1015 |
+
--hash=sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001 \
|
| 1016 |
+
--hash=sha256:1e50f8554d504f617d9e9d6e4c2c2884a12b388a97c5c77f0bc6cf4cd032feee \
|
| 1017 |
+
--hash=sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7 \
|
| 1018 |
+
--hash=sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd \
|
| 1019 |
+
--hash=sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4 \
|
| 1020 |
+
--hash=sha256:319f659ee992222f04e58f84cbf407cfa66a65fe3a8de44e8ad2bc53e7d99012 \
|
| 1021 |
+
--hash=sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67 \
|
| 1022 |
+
--hash=sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a \
|
| 1023 |
+
--hash=sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5 \
|
| 1024 |
+
--hash=sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917 \
|
| 1025 |
+
--hash=sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c \
|
| 1026 |
+
--hash=sha256:64d94e84f6660764e64e7e0b22baa72f6cd942279fdbb21d46abd70d179f0195 \
|
| 1027 |
+
--hash=sha256:753d47ebd4542742ef9261d9da92cd545b2cacbb48349a1225466745bb866ec4 \
|
| 1028 |
+
--hash=sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a \
|
| 1029 |
+
--hash=sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc \
|
| 1030 |
+
--hash=sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92 \
|
| 1031 |
+
--hash=sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5 \
|
| 1032 |
+
--hash=sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48 \
|
| 1033 |
+
--hash=sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b \
|
| 1034 |
+
--hash=sha256:e10bf9113d209be7cd046d40fbabbaf3278ff6d18eb4da4c500443185dc1896c \
|
| 1035 |
+
--hash=sha256:f01a9c019878532f98927d2bacb79bbb404b43d3437455522a00a30718cdedb5
|
| 1036 |
+
# via transformers
|
| 1037 |
+
tqdm==4.70.0 \
|
| 1038 |
+
--hash=sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220 \
|
| 1039 |
+
--hash=sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953
|
| 1040 |
+
# via
|
| 1041 |
+
# -r requirements/bootstrap.in
|
| 1042 |
+
# huggingface-hub
|
| 1043 |
+
# transformers
|
| 1044 |
+
transformers==4.57.1 \
|
| 1045 |
+
--hash=sha256:b10d05da8fa67dc41644dbbf9bc45a44cb86ae33da6f9295f5fbf5b7890bd267 \
|
| 1046 |
+
--hash=sha256:f06c837959196c75039809636cd964b959f6604b75b8eeec6fdfc0440b89cc55
|
| 1047 |
+
# via -r requirements/bootstrap.in
|
| 1048 |
+
trove-classifiers==2026.6.1.19 \
|
| 1049 |
+
--hash=sha256:ab4c4ec93cc4a4e7815fa759906e05e6bb3f2fbd92ea0f897288c6a43efd15b3 \
|
| 1050 |
+
--hash=sha256:c5132b4b61a829d11cfbd2d72e97f20a45ed6edb95e45c5efdeb5e00836b2745
|
| 1051 |
+
# via hatchling
|
| 1052 |
+
typing-extensions==4.16.0 \
|
| 1053 |
+
--hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \
|
| 1054 |
+
--hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5
|
| 1055 |
+
# via
|
| 1056 |
+
# -r requirements/bootstrap.in
|
| 1057 |
+
# huggingface-hub
|
| 1058 |
+
urllib3==2.7.0 \
|
| 1059 |
+
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
|
| 1060 |
+
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
|
| 1061 |
+
# via requests
|
scripts/bootstrap-rocm.sh
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
| 5 |
+
PYTHON_BIN=python3.12
|
| 6 |
+
VENV_DIR="$ROOT/.venv"
|
| 7 |
+
DRY_RUN=0
|
| 8 |
+
DEPENDENCIES_ONLY=0
|
| 9 |
+
LOCK_FILE="$ROOT/requirements/bootstrap.lock"
|
| 10 |
+
|
| 11 |
+
usage() {
|
| 12 |
+
printf '%s\n' \
|
| 13 |
+
"Usage: scripts/bootstrap-rocm.sh [--python PATH] [--venv PATH] [--dependencies-only] [--dry-run]" \
|
| 14 |
+
"" \
|
| 15 |
+
"Creates an isolated Python 3.12 environment with AMD's verified ROCm 7.2.1 wheels." \
|
| 16 |
+
"It never modifies the system Python or the host ROCm installation."
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
while (($#)); do
|
| 20 |
+
case "$1" in
|
| 21 |
+
--python)
|
| 22 |
+
PYTHON_BIN=${2:?--python requires a path}
|
| 23 |
+
shift 2
|
| 24 |
+
;;
|
| 25 |
+
--venv)
|
| 26 |
+
VENV_DIR=${2:?--venv requires a path}
|
| 27 |
+
shift 2
|
| 28 |
+
;;
|
| 29 |
+
--dependencies-only)
|
| 30 |
+
DEPENDENCIES_ONLY=1
|
| 31 |
+
shift
|
| 32 |
+
;;
|
| 33 |
+
-n|--dry-run)
|
| 34 |
+
DRY_RUN=1
|
| 35 |
+
shift
|
| 36 |
+
;;
|
| 37 |
+
-h|--help)
|
| 38 |
+
usage
|
| 39 |
+
exit 0
|
| 40 |
+
;;
|
| 41 |
+
*)
|
| 42 |
+
printf 'error: unknown argument: %s\n' "$1" >&2
|
| 43 |
+
usage >&2
|
| 44 |
+
exit 2
|
| 45 |
+
;;
|
| 46 |
+
esac
|
| 47 |
+
done
|
| 48 |
+
|
| 49 |
+
ROCM_RELEASE=7.2.1
|
| 50 |
+
CACHE_BASE=${XDG_CACHE_HOME:-${HOME}/.cache}/unlimited-ocr-rdna4
|
| 51 |
+
WHEEL_DIR="$CACHE_BASE/wheels/rocm-$ROCM_RELEASE"
|
| 52 |
+
TASK_TMP="$CACHE_BASE/tmp"
|
| 53 |
+
|
| 54 |
+
TORCH_FILE='torch-2.9.1+rocm7.2.1.lw.gitff65f5bc-cp312-cp312-linux_x86_64.whl'
|
| 55 |
+
TORCH_URL='https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2.1/torch-2.9.1%2Brocm7.2.1.lw.gitff65f5bc-cp312-cp312-linux_x86_64.whl'
|
| 56 |
+
TORCH_SHA='fb45ace0a27e9f0d0e3c4c6efd8932162743f8376f2aa4752a4d31ef5a1bd3d7'
|
| 57 |
+
|
| 58 |
+
VISION_FILE='torchvision-0.24.0+rocm7.2.1.gitb919bd0c-cp312-cp312-linux_x86_64.whl'
|
| 59 |
+
VISION_URL='https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2.1/torchvision-0.24.0%2Brocm7.2.1.gitb919bd0c-cp312-cp312-linux_x86_64.whl'
|
| 60 |
+
VISION_SHA='d5fca8cda173235a3b7434baeebe04c3ebffec3c6fc191e79aa8aa300633f2c9'
|
| 61 |
+
|
| 62 |
+
TRITON_FILE='triton-3.5.1+rocm7.2.1.gita272dfa8-cp312-cp312-linux_x86_64.whl'
|
| 63 |
+
TRITON_URL='https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2.1/triton-3.5.1%2Brocm7.2.1.gita272dfa8-cp312-cp312-linux_x86_64.whl'
|
| 64 |
+
TRITON_SHA='07787af1d28c273852f897bfeaa7bca29f2fa4a13ca0f28f535832b240ce7016'
|
| 65 |
+
|
| 66 |
+
if ((DRY_RUN)); then
|
| 67 |
+
printf 'python=%s\nvenv=%s\nrocm_release=%s\nwheel_cache=%s\ndependency_lock=%s\n' \
|
| 68 |
+
"$PYTHON_BIN" "$VENV_DIR" "$ROCM_RELEASE" "$WHEEL_DIR" "$LOCK_FILE"
|
| 69 |
+
printf 'dependencies_only=%s\n' "$DEPENDENCIES_ONLY"
|
| 70 |
+
printf 'download=%s\ndownload=%s\ndownload=%s\n' "$TORCH_URL" "$VISION_URL" "$TRITON_URL"
|
| 71 |
+
printf 'next=%s/bin/unlimited-ocr-rdna4 doctor --device 0\n' "$VENV_DIR"
|
| 72 |
+
exit 0
|
| 73 |
+
fi
|
| 74 |
+
|
| 75 |
+
case "$(uname -s)-$(uname -m)" in
|
| 76 |
+
Linux-x86_64) ;;
|
| 77 |
+
*)
|
| 78 |
+
printf 'error: the verified bootstrap supports Linux x86_64 only\n' >&2
|
| 79 |
+
exit 3
|
| 80 |
+
;;
|
| 81 |
+
esac
|
| 82 |
+
|
| 83 |
+
if [[ ! -r /opt/rocm/.info/version ]]; then
|
| 84 |
+
printf 'error: /opt/rocm/.info/version is missing; install a supported host ROCm stack first\n' >&2
|
| 85 |
+
exit 3
|
| 86 |
+
fi
|
| 87 |
+
HOST_ROCM=$(tr -d '\r\n' < /opt/rocm/.info/version)
|
| 88 |
+
if [[ $HOST_ROCM != "$ROCM_RELEASE" ]]; then
|
| 89 |
+
printf 'error: this bootstrap is verified for host ROCm 7.2.1, found %s\n' "$HOST_ROCM" >&2
|
| 90 |
+
exit 3
|
| 91 |
+
fi
|
| 92 |
+
|
| 93 |
+
PYTHON_VERSION=$("$PYTHON_BIN" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
|
| 94 |
+
if [[ $PYTHON_VERSION != 3.12 ]]; then
|
| 95 |
+
printf 'error: AMD wheel set requires Python 3.12, found %s\n' "$PYTHON_VERSION" >&2
|
| 96 |
+
exit 3
|
| 97 |
+
fi
|
| 98 |
+
command -v curl >/dev/null || { printf 'error: curl is required\n' >&2; exit 3; }
|
| 99 |
+
command -v sha256sum >/dev/null || { printf 'error: sha256sum is required\n' >&2; exit 3; }
|
| 100 |
+
command -v flock >/dev/null || { printf 'error: flock is required\n' >&2; exit 3; }
|
| 101 |
+
[[ -f $LOCK_FILE && ! -L $LOCK_FILE ]] || { printf 'error: dependency lock is missing or symlinked: %s\n' "$LOCK_FILE" >&2; exit 3; }
|
| 102 |
+
|
| 103 |
+
mkdir -p "$WHEEL_DIR" "$TASK_TMP"
|
| 104 |
+
export TMPDIR="$TASK_TMP"
|
| 105 |
+
export PIP_CACHE_DIR="$CACHE_BASE/pip"
|
| 106 |
+
exec 9>"$WHEEL_DIR/.bootstrap.lock"
|
| 107 |
+
flock 9
|
| 108 |
+
|
| 109 |
+
download_wheel() {
|
| 110 |
+
local filename=$1
|
| 111 |
+
local url=$2
|
| 112 |
+
local expected=$3
|
| 113 |
+
local destination="$WHEEL_DIR/$filename"
|
| 114 |
+
local partial="$destination.part"
|
| 115 |
+
|
| 116 |
+
if [[ -L $destination || -L $partial ]]; then
|
| 117 |
+
printf 'error: refusing symlinked wheel cache entry: %s\n' "$destination" >&2
|
| 118 |
+
exit 4
|
| 119 |
+
fi
|
| 120 |
+
if [[ -f $destination ]]; then
|
| 121 |
+
if printf '%s %s\n' "$expected" "$destination" | sha256sum --check --status; then
|
| 122 |
+
printf 'wheel=reused path=%s\n' "$destination"
|
| 123 |
+
return
|
| 124 |
+
fi
|
| 125 |
+
printf 'wheel=discard-corrupt path=%s\n' "$destination" >&2
|
| 126 |
+
rm -f -- "$destination"
|
| 127 |
+
fi
|
| 128 |
+
|
| 129 |
+
for attempt in 1 2; do
|
| 130 |
+
printf 'downloading=%s attempt=%s\n' "$filename" "$attempt"
|
| 131 |
+
if ! curl --fail --location --proto '=https' --tlsv1.2 --continue-at - --output "$partial" "$url"; then
|
| 132 |
+
printf 'wheel=discard-failed-partial path=%s\n' "$partial" >&2
|
| 133 |
+
rm -f -- "$partial"
|
| 134 |
+
continue
|
| 135 |
+
fi
|
| 136 |
+
if printf '%s %s\n' "$expected" "$partial" | sha256sum --check --status; then
|
| 137 |
+
mv "$partial" "$destination"
|
| 138 |
+
return
|
| 139 |
+
fi
|
| 140 |
+
printf 'wheel=discard-corrupt-partial path=%s\n' "$partial" >&2
|
| 141 |
+
rm -f -- "$partial"
|
| 142 |
+
done
|
| 143 |
+
printf 'error: downloaded wheel checksum mismatch after clean retry: %s\n' "$filename" >&2
|
| 144 |
+
exit 4
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
download_wheel "$TORCH_FILE" "$TORCH_URL" "$TORCH_SHA"
|
| 148 |
+
download_wheel "$VISION_FILE" "$VISION_URL" "$VISION_SHA"
|
| 149 |
+
download_wheel "$TRITON_FILE" "$TRITON_URL" "$TRITON_SHA"
|
| 150 |
+
|
| 151 |
+
validate_venv() {
|
| 152 |
+
if [[ -L $VENV_DIR || ! -d $VENV_DIR || -L $VENV_DIR/pyvenv.cfg || ! -f $VENV_DIR/pyvenv.cfg ]]; then
|
| 153 |
+
printf 'error: existing --venv path is not a regular Python virtual environment: %s\n' "$VENV_DIR" >&2
|
| 154 |
+
exit 3
|
| 155 |
+
fi
|
| 156 |
+
if [[ ! -x $VENV_DIR/bin/python ]]; then
|
| 157 |
+
printf 'error: virtual environment has no executable bin/python: %s\n' "$VENV_DIR" >&2
|
| 158 |
+
exit 3
|
| 159 |
+
fi
|
| 160 |
+
"$VENV_DIR/bin/python" - "$VENV_DIR" <<'PY'
|
| 161 |
+
import os
|
| 162 |
+
import sys
|
| 163 |
+
|
| 164 |
+
expected = os.path.realpath(sys.argv[1])
|
| 165 |
+
if os.path.realpath(sys.prefix) != expected or sys.prefix == sys.base_prefix:
|
| 166 |
+
raise SystemExit("virtual environment prefix validation failed")
|
| 167 |
+
if sys.version_info[:2] != (3, 12):
|
| 168 |
+
raise SystemExit(f"virtual environment requires Python 3.12, found {sys.version_info.major}.{sys.version_info.minor}")
|
| 169 |
+
PY
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
if [[ -L $VENV_DIR ]]; then
|
| 173 |
+
printf 'error: --venv path must not be a symlink: %s\n' "$VENV_DIR" >&2
|
| 174 |
+
exit 3
|
| 175 |
+
fi
|
| 176 |
+
if [[ -e $VENV_DIR && ! -d $VENV_DIR ]]; then
|
| 177 |
+
printf 'error: --venv path exists and is not a directory: %s\n' "$VENV_DIR" >&2
|
| 178 |
+
exit 3
|
| 179 |
+
fi
|
| 180 |
+
if [[ ! -e $VENV_DIR ]]; then
|
| 181 |
+
"$PYTHON_BIN" -m venv "$VENV_DIR"
|
| 182 |
+
fi
|
| 183 |
+
validate_venv
|
| 184 |
+
"$VENV_DIR/bin/python" -m pip install --require-hashes --only-binary=:all: -r "$LOCK_FILE"
|
| 185 |
+
"$VENV_DIR/bin/python" -m pip install \
|
| 186 |
+
--no-deps \
|
| 187 |
+
"$WHEEL_DIR/$TORCH_FILE" \
|
| 188 |
+
"$WHEEL_DIR/$VISION_FILE" \
|
| 189 |
+
"$WHEEL_DIR/$TRITON_FILE"
|
| 190 |
+
if ((DEPENDENCIES_ONLY)); then
|
| 191 |
+
"$VENV_DIR/bin/python" -m pip check
|
| 192 |
+
printf 'bootstrap=PASS mode=dependencies-only venv=%s\n' "$VENV_DIR"
|
| 193 |
+
exit 0
|
| 194 |
+
fi
|
| 195 |
+
BUILD_DIR=$(mktemp -d "$TASK_TMP/package-build.XXXXXX")
|
| 196 |
+
trap 'rm -rf -- "$BUILD_DIR"' EXIT
|
| 197 |
+
"$VENV_DIR/bin/python" -m pip wheel --no-build-isolation --no-deps --wheel-dir "$BUILD_DIR" "$ROOT"
|
| 198 |
+
PACKAGE_WHEEL=$(find "$BUILD_DIR" -maxdepth 1 -type f -name 'unlimited_ocr_rdna4-*.whl' -print -quit)
|
| 199 |
+
[[ -n $PACKAGE_WHEEL ]] || { printf 'error: local package wheel was not produced\n' >&2; exit 4; }
|
| 200 |
+
"$VENV_DIR/bin/python" -m pip install --force-reinstall --no-deps "$PACKAGE_WHEEL"
|
| 201 |
+
"$VENV_DIR/bin/python" -m pip check
|
| 202 |
+
"$VENV_DIR/bin/unlimited-ocr-rdna4" doctor --device "${UNLIMITED_OCR_DEVICE:-0}"
|
| 203 |
+
printf 'bootstrap=PASS venv=%s\n' "$VENV_DIR"
|
scripts/check-validation.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import argparse
|
| 5 |
+
import copy
|
| 6 |
+
import hashlib
|
| 7 |
+
import json
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
from unlimited_ocr_rdna4.constants import MODEL_REVISION, VERIFIED_HIP_VERSION, VERIFIED_PACKAGE_VERSIONS
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def _load_json(path: Path) -> dict:
|
| 14 |
+
value = json.loads(path.read_text(encoding="utf-8"))
|
| 15 |
+
if not isinstance(value, dict):
|
| 16 |
+
raise AssertionError(f"expected a JSON object: {path}")
|
| 17 |
+
return value
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _sha256(path: Path) -> str:
|
| 21 |
+
return hashlib.sha256(path.read_bytes()).hexdigest()
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def _check_output(path: Path) -> None:
|
| 25 |
+
text = path.read_text(encoding="utf-8")
|
| 26 |
+
ordered = (
|
| 27 |
+
"Unlimited OCR RDNA4 Smoke Test",
|
| 28 |
+
"Items",
|
| 29 |
+
"Document scan",
|
| 30 |
+
"Table extraction",
|
| 31 |
+
"Total",
|
| 32 |
+
"Verification notes",
|
| 33 |
+
"RDNA4-OCR-PASS",
|
| 34 |
+
)
|
| 35 |
+
positions = [text.index(marker) for marker in ordered]
|
| 36 |
+
assert positions == sorted(positions), f"reading order mismatch in {path}"
|
| 37 |
+
assert "€48.50" in text, f"euro table total missing from {path}"
|
| 38 |
+
assert "mc}^2" in text or "mc^2" in text, f"formula missing from {path}"
|
| 39 |
+
assert "<table" in text or "| Item" in text, f"structured table missing from {path}"
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _check_run(payload: dict, *, expected_pages: int) -> None:
|
| 43 |
+
assert payload["schema_version"] == 1
|
| 44 |
+
assert payload["status"] == "ok"
|
| 45 |
+
assert payload["model_revision"] == MODEL_REVISION
|
| 46 |
+
assert payload["architecture"] == "gfx1201"
|
| 47 |
+
assert payload["hardware_verified"] is True
|
| 48 |
+
assert payload["software_verified"] is True
|
| 49 |
+
assert payload["pages_processed"] == expected_pages
|
| 50 |
+
assert payload["pages_total"] == expected_pages
|
| 51 |
+
assert 0 < payload["peak_allocated_gib"] < 16
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def main() -> None:
|
| 55 |
+
parser = argparse.ArgumentParser(description="Validate machine-readable RDNA 4 smoke evidence")
|
| 56 |
+
parser.add_argument("--doctor", type=Path, required=True)
|
| 57 |
+
parser.add_argument("--image-run", type=Path, action="append", required=True)
|
| 58 |
+
parser.add_argument("--pdf-run", type=Path, required=True)
|
| 59 |
+
parser.add_argument("--image-output", type=Path, action="append", required=True)
|
| 60 |
+
parser.add_argument("--pdf-output", type=Path, required=True)
|
| 61 |
+
parser.add_argument("--expected-image-sha256")
|
| 62 |
+
parser.add_argument("--expected-pdf-sha256")
|
| 63 |
+
parser.add_argument("--network-isolated", action="store_true")
|
| 64 |
+
parser.add_argument("--gpu-processes", type=Path)
|
| 65 |
+
parser.add_argument("--evidence-output", type=Path)
|
| 66 |
+
args = parser.parse_args()
|
| 67 |
+
|
| 68 |
+
assert len(args.image_run) == 2, "exactly two image-run JSON files are required"
|
| 69 |
+
assert len(args.image_output) == 2, "exactly two image outputs are required"
|
| 70 |
+
doctor = _load_json(args.doctor)
|
| 71 |
+
assert doctor["schema_version"] == 1
|
| 72 |
+
assert doctor["status"] == "ok"
|
| 73 |
+
assert doctor["runtime_issues"] == []
|
| 74 |
+
runtime = doctor["runtime"]
|
| 75 |
+
assert runtime["visible_devices"] == 1
|
| 76 |
+
assert runtime["accepted_architecture"] is True
|
| 77 |
+
assert runtime["hardware_verified"] is True
|
| 78 |
+
assert runtime["software_verified"] is True
|
| 79 |
+
assert runtime["hip_version"] == VERIFIED_HIP_VERSION
|
| 80 |
+
assert runtime["package_versions"] == VERIFIED_PACKAGE_VERSIONS
|
| 81 |
+
assert doctor["model"]["prepared"] is True
|
| 82 |
+
assert doctor["model"]["revision"] == MODEL_REVISION
|
| 83 |
+
|
| 84 |
+
image_runs = [_load_json(run_path) for run_path in args.image_run]
|
| 85 |
+
pdf_run = _load_json(args.pdf_run)
|
| 86 |
+
for image_run in image_runs:
|
| 87 |
+
_check_run(image_run, expected_pages=1)
|
| 88 |
+
_check_run(pdf_run, expected_pages=1)
|
| 89 |
+
for output_path in (*args.image_output, args.pdf_output):
|
| 90 |
+
_check_output(output_path)
|
| 91 |
+
|
| 92 |
+
image_hashes = [_sha256(path) for path in args.image_output]
|
| 93 |
+
assert image_hashes[0] == image_hashes[1], "fresh-process image outputs are not byte-identical"
|
| 94 |
+
pdf_hash = _sha256(args.pdf_output)
|
| 95 |
+
if args.expected_image_sha256:
|
| 96 |
+
assert image_hashes[0] == args.expected_image_sha256
|
| 97 |
+
if args.expected_pdf_sha256:
|
| 98 |
+
assert pdf_hash == args.expected_pdf_sha256
|
| 99 |
+
if args.network_isolated:
|
| 100 |
+
assert args.network_isolated is True
|
| 101 |
+
processes = _load_json_array(args.gpu_processes) if args.gpu_processes else None
|
| 102 |
+
if processes is not None:
|
| 103 |
+
names = []
|
| 104 |
+
for gpu in processes:
|
| 105 |
+
for process in gpu.get("process_list", []):
|
| 106 |
+
process_info = process.get("process_info")
|
| 107 |
+
if isinstance(process_info, dict) and isinstance(process_info.get("name"), str):
|
| 108 |
+
names.append(process_info["name"])
|
| 109 |
+
assert not any("python" in name.lower() for name in names), f"OCR Python process still owns VRAM: {names}"
|
| 110 |
+
|
| 111 |
+
summary = {
|
| 112 |
+
"schema_version": 1,
|
| 113 |
+
"validation": "PASS",
|
| 114 |
+
"network_isolated": args.network_isolated,
|
| 115 |
+
"image_sha256": image_hashes[0],
|
| 116 |
+
"pdf_sha256": pdf_hash,
|
| 117 |
+
}
|
| 118 |
+
if args.evidence_output:
|
| 119 |
+
doctor_evidence = copy.deepcopy(doctor)
|
| 120 |
+
doctor_evidence["model"]["model_dir"] = "<prepared-model>"
|
| 121 |
+
image_evidence = copy.deepcopy(image_runs)
|
| 122 |
+
for index, image_run in enumerate(image_evidence, start=1):
|
| 123 |
+
image_run["input"] = "tests/fixtures/rdna4-smoke.png"
|
| 124 |
+
image_run["output"] = f"<temporary-output>/smoke-image-{index}.md"
|
| 125 |
+
pdf_evidence = copy.deepcopy(pdf_run)
|
| 126 |
+
pdf_evidence["input"] = "tests/fixtures/rdna4-smoke.pdf"
|
| 127 |
+
pdf_evidence["output"] = "<temporary-output>/smoke-pdf.md"
|
| 128 |
+
evidence = {
|
| 129 |
+
**summary,
|
| 130 |
+
"fixtures": {
|
| 131 |
+
"image_sha256": "f5099e17be868abfb4213dbdab220deac82a2db93ba87ab22f03219178246972",
|
| 132 |
+
"pdf_sha256": "cdd2b484d0ac90bd98b489dd97565a65eb17246f713359524cddd41d78cc10cb",
|
| 133 |
+
},
|
| 134 |
+
"doctor": doctor_evidence,
|
| 135 |
+
"image_runs": image_evidence,
|
| 136 |
+
"pdf_run": pdf_evidence,
|
| 137 |
+
"gpu_processes_after": processes,
|
| 138 |
+
}
|
| 139 |
+
args.evidence_output.parent.mkdir(parents=True, exist_ok=True)
|
| 140 |
+
args.evidence_output.write_text(json.dumps(evidence, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
| 141 |
+
print(json.dumps(summary, sort_keys=True))
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def _load_json_array(path: Path) -> list[dict]:
|
| 145 |
+
value = json.loads(path.read_text(encoding="utf-8"))
|
| 146 |
+
if not isinstance(value, list):
|
| 147 |
+
raise AssertionError(f"expected a JSON array: {path}")
|
| 148 |
+
return value
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
if __name__ == "__main__":
|
| 152 |
+
main()
|
scripts/make-smoke-fixture.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import argparse
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def resolve_font_pair(regular_override: Path | None, bold_override: Path | None) -> tuple[Path, Path]:
|
| 11 |
+
if (regular_override is None) != (bold_override is None):
|
| 12 |
+
raise SystemExit("--regular-font and --bold-font must be supplied together")
|
| 13 |
+
if regular_override and bold_override:
|
| 14 |
+
if not regular_override.is_file() or not bold_override.is_file():
|
| 15 |
+
raise SystemExit("one or both explicit font paths do not exist")
|
| 16 |
+
return regular_override, bold_override
|
| 17 |
+
|
| 18 |
+
families = (
|
| 19 |
+
("DejaVuSans.ttf", "DejaVuSans-Bold.ttf"),
|
| 20 |
+
("LiberationSans-Regular.ttf", "LiberationSans-Bold.ttf"),
|
| 21 |
+
)
|
| 22 |
+
roots = (Path("/usr/share/fonts"), Path("/usr/local/share/fonts"))
|
| 23 |
+
for regular_name, bold_name in families:
|
| 24 |
+
for root in roots:
|
| 25 |
+
regular_matches = sorted(root.rglob(regular_name)) if root.is_dir() else []
|
| 26 |
+
bold_matches = sorted(root.rglob(bold_name)) if root.is_dir() else []
|
| 27 |
+
if regular_matches and bold_matches:
|
| 28 |
+
return regular_matches[0], bold_matches[0]
|
| 29 |
+
raise SystemExit("no DejaVu Sans or Liberation Sans font pair found; pass --regular-font and --bold-font")
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def main() -> None:
|
| 33 |
+
parser = argparse.ArgumentParser(description="Generate a deterministic OCR validation page")
|
| 34 |
+
parser.add_argument("--output", type=Path, required=True)
|
| 35 |
+
parser.add_argument("--pdf-output", type=Path, help="also write the page as a 150-DPI PDF")
|
| 36 |
+
parser.add_argument("--regular-font", type=Path, help="regular TrueType/OpenType font override")
|
| 37 |
+
parser.add_argument("--bold-font", type=Path, help="bold TrueType/OpenType font override")
|
| 38 |
+
args = parser.parse_args()
|
| 39 |
+
|
| 40 |
+
regular_path, bold_path = resolve_font_pair(args.regular_font, args.bold_font)
|
| 41 |
+
image = Image.new("RGB", (1600, 2000), "white")
|
| 42 |
+
draw = ImageDraw.Draw(image)
|
| 43 |
+
title = ImageFont.truetype(str(bold_path), 68)
|
| 44 |
+
heading = ImageFont.truetype(str(bold_path), 38)
|
| 45 |
+
body = ImageFont.truetype(str(regular_path), 34)
|
| 46 |
+
small = ImageFont.truetype(str(regular_path), 28)
|
| 47 |
+
|
| 48 |
+
draw.text((120, 100), "Unlimited OCR RDNA4 Smoke Test", font=title, fill="black")
|
| 49 |
+
draw.line((120, 205, 1480, 205), fill="black", width=4)
|
| 50 |
+
draw.text((120, 265), "Invoice No. OCR-2026-0731", font=body, fill="black")
|
| 51 |
+
draw.text((120, 325), "Date: 31 July 2026", font=body, fill="black")
|
| 52 |
+
draw.text((120, 385), "GPU target: AMD gfx1201", font=body, fill="black")
|
| 53 |
+
|
| 54 |
+
draw.text((120, 505), "Items", font=heading, fill="black")
|
| 55 |
+
left, top, right, bottom = 120, 580, 1480, 1050
|
| 56 |
+
columns = (left, 930, 1120, right)
|
| 57 |
+
rows = (top, 690, 810, 930, bottom)
|
| 58 |
+
for x in columns:
|
| 59 |
+
draw.line((x, top, x, bottom), fill="black", width=3)
|
| 60 |
+
for y in rows:
|
| 61 |
+
draw.line((left, y, right, y), fill="black", width=3)
|
| 62 |
+
draw.text((145, 610), "Item", font=heading, fill="black")
|
| 63 |
+
draw.text((960, 610), "Qty", font=heading, fill="black")
|
| 64 |
+
draw.text((1150, 610), "Price", font=heading, fill="black")
|
| 65 |
+
draw.text((145, 725), "Document scan", font=body, fill="black")
|
| 66 |
+
draw.text((980, 725), "2", font=body, fill="black")
|
| 67 |
+
draw.text((1150, 725), "€19.50", font=body, fill="black")
|
| 68 |
+
draw.text((145, 845), "Table extraction", font=body, fill="black")
|
| 69 |
+
draw.text((980, 845), "1", font=body, fill="black")
|
| 70 |
+
draw.text((1150, 845), "€9.50", font=body, fill="black")
|
| 71 |
+
draw.text((145, 965), "Total", font=heading, fill="black")
|
| 72 |
+
draw.text((1150, 965), "€48.50", font=heading, fill="black")
|
| 73 |
+
|
| 74 |
+
draw.text((120, 1190), "Verification notes", font=heading, fill="black")
|
| 75 |
+
notes = [
|
| 76 |
+
"• AMD Radeon RX 9070 XT",
|
| 77 |
+
"• ROCm architecture: gfx1201",
|
| 78 |
+
"• Expected checksum: RDNA4-OCR-PASS",
|
| 79 |
+
"• Formula sample: E = mc²",
|
| 80 |
+
]
|
| 81 |
+
for index, note in enumerate(notes):
|
| 82 |
+
draw.text((150, 1270 + index * 70), note, font=body, fill="black")
|
| 83 |
+
|
| 84 |
+
draw.line((120, 1780, 1480, 1780), fill="black", width=2)
|
| 85 |
+
draw.text((120, 1820), "Synthetic validation page; no private data.", font=small, fill="black")
|
| 86 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 87 |
+
image.save(args.output, format="PNG", optimize=True)
|
| 88 |
+
print(args.output)
|
| 89 |
+
|
| 90 |
+
if args.pdf_output:
|
| 91 |
+
args.pdf_output.parent.mkdir(parents=True, exist_ok=True)
|
| 92 |
+
image.save(args.pdf_output, format="PDF", resolution=150.0)
|
| 93 |
+
print(args.pdf_output)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
if __name__ == "__main__":
|
| 97 |
+
main()
|
scripts/validate-smoke.sh
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
| 5 |
+
CLI=${CLI:-$ROOT/.venv/bin/unlimited-ocr-rdna4}
|
| 6 |
+
PYTHON=${PYTHON:-$ROOT/.venv/bin/python}
|
| 7 |
+
DEVICE=${UNLIMITED_OCR_DEVICE:-0}
|
| 8 |
+
MODEL_DIR=${UNLIMITED_OCR_MODEL_DIR:-}
|
| 9 |
+
AMD_SMI_GPU=${AMD_SMI_GPU:-}
|
| 10 |
+
NETWORK_ISOLATED=${VALIDATION_NETWORK_ISOLATED:-0}
|
| 11 |
+
EXPECTED_IMAGE_SHA256=${EXPECTED_IMAGE_SHA256:-13df40051aceb3bd14f75622697dda1f2a96515bb95c0483e0920cd4d41cbc01}
|
| 12 |
+
EXPECTED_PDF_SHA256=${EXPECTED_PDF_SHA256:-fb17a6396c3e73a025e388a09f73357960ca0bf95124e38179e06a2e64442438}
|
| 13 |
+
EVIDENCE_OUTPUT=${VALIDATION_EVIDENCE_OUTPUT:-}
|
| 14 |
+
CACHE_BASE=${XDG_CACHE_HOME:-${HOME}/.cache}/unlimited-ocr-rdna4
|
| 15 |
+
FIXTURE_IMAGE="$ROOT/tests/fixtures/rdna4-smoke.png"
|
| 16 |
+
FIXTURE_PDF="$ROOT/tests/fixtures/rdna4-smoke.pdf"
|
| 17 |
+
mkdir -p "$CACHE_BASE"
|
| 18 |
+
WORK_DIR=$(mktemp -d "$CACHE_BASE/validation.XXXXXX")
|
| 19 |
+
trap 'rm -rf -- "$WORK_DIR"' EXIT
|
| 20 |
+
|
| 21 |
+
printf '%s %s\n' \
|
| 22 |
+
'f5099e17be868abfb4213dbdab220deac82a2db93ba87ab22f03219178246972' "$FIXTURE_IMAGE" \
|
| 23 |
+
'cdd2b484d0ac90bd98b489dd97565a65eb17246f713359524cddd41d78cc10cb' "$FIXTURE_PDF" |
|
| 24 |
+
sha256sum --check --status
|
| 25 |
+
|
| 26 |
+
MODEL_ARGS=()
|
| 27 |
+
if [[ -n $MODEL_DIR ]]; then
|
| 28 |
+
MODEL_ARGS=(--model-dir "$MODEL_DIR")
|
| 29 |
+
fi
|
| 30 |
+
|
| 31 |
+
run_isolated() {
|
| 32 |
+
sudo -n systemd-run \
|
| 33 |
+
--quiet --wait --pipe --collect \
|
| 34 |
+
-p PrivateNetwork=yes \
|
| 35 |
+
-p "User=$(id -un)" \
|
| 36 |
+
-p "WorkingDirectory=$ROOT" \
|
| 37 |
+
-p "Environment=HOME=$HOME" \
|
| 38 |
+
-- "$@"
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
run_ocr() {
|
| 42 |
+
if [[ $NETWORK_ISOLATED == 1 ]]; then
|
| 43 |
+
run_isolated "$@"
|
| 44 |
+
else
|
| 45 |
+
"$@"
|
| 46 |
+
fi
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
if [[ $NETWORK_ISOLATED == 1 ]]; then
|
| 50 |
+
if run_isolated /usr/bin/python3 -c \
|
| 51 |
+
'import socket; socket.create_connection(("1.1.1.1", 80), timeout=0.25)' \
|
| 52 |
+
>"$WORK_DIR/network-probe.out" 2>"$WORK_DIR/network-probe.err"; then
|
| 53 |
+
printf 'error: PrivateNetwork validation probe unexpectedly reached the network\n' >&2
|
| 54 |
+
exit 5
|
| 55 |
+
fi
|
| 56 |
+
fi
|
| 57 |
+
|
| 58 |
+
"$CLI" doctor --device "$DEVICE" --require-model --json "${MODEL_ARGS[@]}" >"$WORK_DIR/doctor.json"
|
| 59 |
+
|
| 60 |
+
run_ocr "$CLI" run \
|
| 61 |
+
--input "$FIXTURE_IMAGE" \
|
| 62 |
+
--output "$WORK_DIR/smoke-image-1.md" \
|
| 63 |
+
--device "$DEVICE" \
|
| 64 |
+
--json \
|
| 65 |
+
"${MODEL_ARGS[@]}" >"$WORK_DIR/image-1.json"
|
| 66 |
+
|
| 67 |
+
run_ocr "$CLI" run \
|
| 68 |
+
--input "$FIXTURE_IMAGE" \
|
| 69 |
+
--output "$WORK_DIR/smoke-image-2.md" \
|
| 70 |
+
--device "$DEVICE" \
|
| 71 |
+
--json \
|
| 72 |
+
"${MODEL_ARGS[@]}" >"$WORK_DIR/image-2.json"
|
| 73 |
+
|
| 74 |
+
run_ocr "$CLI" run \
|
| 75 |
+
--input "$FIXTURE_PDF" \
|
| 76 |
+
--output "$WORK_DIR/smoke-pdf.md" \
|
| 77 |
+
--device "$DEVICE" \
|
| 78 |
+
--dpi 150 \
|
| 79 |
+
--max-pages 1 \
|
| 80 |
+
--json \
|
| 81 |
+
"${MODEL_ARGS[@]}" >"$WORK_DIR/pdf.json"
|
| 82 |
+
|
| 83 |
+
if [[ -n $AMD_SMI_GPU ]]; then
|
| 84 |
+
amd-smi process -g "$AMD_SMI_GPU" --json >"$WORK_DIR/gpu-processes.json"
|
| 85 |
+
fi
|
| 86 |
+
|
| 87 |
+
CHECK_ARGS=(
|
| 88 |
+
--doctor "$WORK_DIR/doctor.json"
|
| 89 |
+
--image-run "$WORK_DIR/image-1.json"
|
| 90 |
+
--image-run "$WORK_DIR/image-2.json"
|
| 91 |
+
--pdf-run "$WORK_DIR/pdf.json"
|
| 92 |
+
--image-output "$WORK_DIR/smoke-image-1.md"
|
| 93 |
+
--image-output "$WORK_DIR/smoke-image-2.md"
|
| 94 |
+
--pdf-output "$WORK_DIR/smoke-pdf.md"
|
| 95 |
+
)
|
| 96 |
+
CHECK_ARGS+=(--expected-image-sha256 "$EXPECTED_IMAGE_SHA256")
|
| 97 |
+
CHECK_ARGS+=(--expected-pdf-sha256 "$EXPECTED_PDF_SHA256")
|
| 98 |
+
if [[ $NETWORK_ISOLATED == 1 ]]; then
|
| 99 |
+
CHECK_ARGS+=(--network-isolated)
|
| 100 |
+
fi
|
| 101 |
+
if [[ -n $AMD_SMI_GPU ]]; then
|
| 102 |
+
CHECK_ARGS+=(--gpu-processes "$WORK_DIR/gpu-processes.json")
|
| 103 |
+
fi
|
| 104 |
+
if [[ -n $EVIDENCE_OUTPUT ]]; then
|
| 105 |
+
CHECK_ARGS+=(--evidence-output "$EVIDENCE_OUTPUT")
|
| 106 |
+
fi
|
| 107 |
+
|
| 108 |
+
"$PYTHON" "$ROOT/scripts/check-validation.py" "${CHECK_ARGS[@]}"
|
src/unlimited_ocr_rdna4/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unlimited-OCR runtime for AMD RDNA 4 GPUs."""
|
| 2 |
+
|
| 3 |
+
from .constants import VERSION
|
| 4 |
+
|
| 5 |
+
__all__ = ["VERSION"]
|
| 6 |
+
__version__ = VERSION
|
src/unlimited_ocr_rdna4/__main__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .cli import main
|
| 2 |
+
|
| 3 |
+
if __name__ == "__main__":
|
| 4 |
+
main()
|
src/unlimited_ocr_rdna4/cli.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import json
|
| 5 |
+
import sys
|
| 6 |
+
import traceback
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
from .constants import MODEL_REVISION, VERSION
|
| 10 |
+
from .errors import AppError, ModelIntegrityError
|
| 11 |
+
from .infer import RunOptions, default_output_path, run_inference
|
| 12 |
+
from .model_store import prepare_model, verify_model
|
| 13 |
+
from .paths import default_cache_dir, default_model_dir
|
| 14 |
+
from .runtime import inspect_runtime, runtime_issues
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def bounded_int(minimum: int, maximum: int):
|
| 18 |
+
def parse(value: str) -> int:
|
| 19 |
+
number = int(value)
|
| 20 |
+
if not minimum <= number <= maximum:
|
| 21 |
+
raise argparse.ArgumentTypeError(f"must be between {minimum} and {maximum}")
|
| 22 |
+
return number
|
| 23 |
+
|
| 24 |
+
return parse
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _add_output_flags(parser: argparse.ArgumentParser, *, suppress_defaults: bool = False) -> None:
|
| 28 |
+
default = argparse.SUPPRESS if suppress_defaults else False
|
| 29 |
+
parser.add_argument("--json", action="store_true", default=default, help="emit structured JSON")
|
| 30 |
+
parser.add_argument("-q", "--quiet", action="store_true", default=default, help="suppress progress diagnostics")
|
| 31 |
+
parser.add_argument("--debug", action="store_true", default=default, help="show tracebacks for unexpected failures")
|
| 32 |
+
parser.add_argument(
|
| 33 |
+
"--no-color", action="store_true", default=default, help="disable color (currently the default)"
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def build_parser() -> argparse.ArgumentParser:
|
| 38 |
+
parser = argparse.ArgumentParser(
|
| 39 |
+
prog="unlimited-ocr-rdna4",
|
| 40 |
+
description="Run Baidu Unlimited-OCR locally on a single AMD RDNA 4 GPU.",
|
| 41 |
+
epilog="Examples: unlimited-ocr-rdna4 prepare; unlimited-ocr-rdna4 run --input page.png",
|
| 42 |
+
)
|
| 43 |
+
_add_output_flags(parser)
|
| 44 |
+
parser.add_argument("--version", action="version", version=VERSION)
|
| 45 |
+
subparsers = parser.add_subparsers(dest="command")
|
| 46 |
+
|
| 47 |
+
prepare = subparsers.add_parser("prepare", help="download and verify the pinned Baidu model")
|
| 48 |
+
_add_output_flags(prepare, suppress_defaults=True)
|
| 49 |
+
prepare.add_argument("--model-dir", type=Path, default=default_model_dir(), help="prepared model destination")
|
| 50 |
+
prepare.add_argument("--cache-dir", type=Path, default=default_cache_dir(), help="persistent download cache")
|
| 51 |
+
prepare.add_argument("-n", "--dry-run", action="store_true", help="show the plan without downloading")
|
| 52 |
+
|
| 53 |
+
doctor = subparsers.add_parser("doctor", help="inspect ROCm, PyTorch, RDNA 4, and model readiness")
|
| 54 |
+
_add_output_flags(doctor, suppress_defaults=True)
|
| 55 |
+
doctor.add_argument("--device", help="ROCm ordinal or stable ROCr UUID; omit to inspect all visible GPUs")
|
| 56 |
+
doctor.add_argument("--model-dir", type=Path, default=default_model_dir(), help="prepared model directory")
|
| 57 |
+
doctor.add_argument("--require-model", action="store_true", help="fail when the prepared model is absent")
|
| 58 |
+
|
| 59 |
+
run = subparsers.add_parser("run", help="parse one image or PDF into Markdown")
|
| 60 |
+
_add_output_flags(run, suppress_defaults=True)
|
| 61 |
+
run.add_argument("--input", required=True, type=Path, help="input image or PDF")
|
| 62 |
+
run.add_argument("-o", "--output", type=Path, help="Markdown output path")
|
| 63 |
+
run.add_argument(
|
| 64 |
+
"--device",
|
| 65 |
+
default=None,
|
| 66 |
+
help="ROCm ordinal or stable ROCr UUID (default: UNLIMITED_OCR_DEVICE or 0)",
|
| 67 |
+
)
|
| 68 |
+
run.add_argument("--model-dir", type=Path, default=default_model_dir(), help="prepared model directory")
|
| 69 |
+
run.add_argument("--mode", choices=("gundam", "base"), default="gundam", help="single-page image profile")
|
| 70 |
+
run.add_argument("--max-length", type=bounded_int(512, 32768), default=4096, help="total sequence limit")
|
| 71 |
+
run.add_argument("--dpi", type=bounded_int(72, 400), default=200, help="PDF rendering resolution")
|
| 72 |
+
run.add_argument("--start-page", type=bounded_int(1, 100000), default=1, help="first PDF page, 1-based")
|
| 73 |
+
run.add_argument("--max-pages", type=bounded_int(1, 100), default=20, help="maximum PDF pages per run")
|
| 74 |
+
run.add_argument(
|
| 75 |
+
"--max-page-pixels",
|
| 76 |
+
type=bounded_int(1_000_000, 200_000_000),
|
| 77 |
+
default=60_000_000,
|
| 78 |
+
help="maximum rendered pixels for one PDF page",
|
| 79 |
+
)
|
| 80 |
+
run.add_argument(
|
| 81 |
+
"--max-total-pixels",
|
| 82 |
+
type=bounded_int(1_000_000, 2_000_000_000),
|
| 83 |
+
default=400_000_000,
|
| 84 |
+
help="maximum aggregate rendered pixels for a PDF run",
|
| 85 |
+
)
|
| 86 |
+
run.add_argument(
|
| 87 |
+
"--max-page-rendered-mib",
|
| 88 |
+
type=bounded_int(1, 2048),
|
| 89 |
+
default=512,
|
| 90 |
+
help="maximum temporary size of one rendered PDF page",
|
| 91 |
+
)
|
| 92 |
+
run.add_argument(
|
| 93 |
+
"--max-rendered-mib",
|
| 94 |
+
type=bounded_int(1, 8192),
|
| 95 |
+
default=2048,
|
| 96 |
+
help="maximum aggregate size of rendered PDF pages",
|
| 97 |
+
)
|
| 98 |
+
run.add_argument("--prompt", default="<image>document parsing.", help="model prompt")
|
| 99 |
+
run.add_argument("-f", "--force", action="store_true", help="replace an existing output file")
|
| 100 |
+
return parser
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def _emit(payload: dict, *, as_json: bool, quiet: bool = False) -> None:
|
| 104 |
+
if as_json:
|
| 105 |
+
print(json.dumps(payload, ensure_ascii=False, sort_keys=True))
|
| 106 |
+
return
|
| 107 |
+
if quiet:
|
| 108 |
+
primary = payload.get("output") or payload.get("model_dir") or payload.get("status")
|
| 109 |
+
if primary is not None:
|
| 110 |
+
print(primary)
|
| 111 |
+
return
|
| 112 |
+
for key, value in payload.items():
|
| 113 |
+
if isinstance(value, (dict, list, tuple)):
|
| 114 |
+
print(f"{key}={json.dumps(value, ensure_ascii=False, sort_keys=True)}")
|
| 115 |
+
else:
|
| 116 |
+
print(f"{key}={value}")
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def _command_prepare(args: argparse.Namespace) -> int:
|
| 120 |
+
if not args.quiet:
|
| 121 |
+
action = "Would prepare" if args.dry_run else "Preparing"
|
| 122 |
+
print(f"{action} {MODEL_REVISION} at {args.model_dir}", file=sys.stderr, flush=True)
|
| 123 |
+
status = prepare_model(args.model_dir, args.cache_dir, dry_run=args.dry_run)
|
| 124 |
+
payload = {"schema_version": 1, **status.__dict__, "status": "dry-run" if args.dry_run else "ok"}
|
| 125 |
+
_emit(payload, as_json=args.json, quiet=args.quiet)
|
| 126 |
+
return 0
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def _command_doctor(args: argparse.Namespace) -> int:
|
| 130 |
+
runtime = inspect_runtime(device=args.device, require_single=False, validate=False)
|
| 131 |
+
issues = runtime_issues(runtime, require_single=False)
|
| 132 |
+
model_payload: dict[str, object]
|
| 133 |
+
try:
|
| 134 |
+
model = verify_model(args.model_dir.expanduser().resolve(), full_weight_hash=False)
|
| 135 |
+
model_payload = {"prepared": True, "model_dir": model.model_dir, "revision": model.revision}
|
| 136 |
+
except ModelIntegrityError as exc:
|
| 137 |
+
if args.require_model:
|
| 138 |
+
raise
|
| 139 |
+
model_payload = {"prepared": False, "model_dir": str(args.model_dir), "detail": str(exc)}
|
| 140 |
+
payload = {
|
| 141 |
+
"schema_version": 1,
|
| 142 |
+
"status": "ok" if not issues else "error",
|
| 143 |
+
"runtime": runtime.to_dict(),
|
| 144 |
+
"runtime_issues": issues,
|
| 145 |
+
"model": model_payload,
|
| 146 |
+
}
|
| 147 |
+
_emit(payload, as_json=args.json, quiet=args.quiet)
|
| 148 |
+
return 0 if not issues else 3
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def _command_run(args: argparse.Namespace) -> int:
|
| 152 |
+
import os
|
| 153 |
+
|
| 154 |
+
device = args.device or os.environ.get("UNLIMITED_OCR_DEVICE")
|
| 155 |
+
visibility_is_preconfigured = any(
|
| 156 |
+
name in os.environ for name in ("ROCR_VISIBLE_DEVICES", "HIP_VISIBLE_DEVICES", "CUDA_VISIBLE_DEVICES")
|
| 157 |
+
)
|
| 158 |
+
if device is None and not visibility_is_preconfigured:
|
| 159 |
+
device = "0"
|
| 160 |
+
runtime = inspect_runtime(device=device, require_single=True)
|
| 161 |
+
input_path = args.input.expanduser()
|
| 162 |
+
output = args.output.expanduser() if args.output else default_output_path(input_path)
|
| 163 |
+
if not args.quiet:
|
| 164 |
+
print(f"Loading pinned model on RDNA 4 device {device}; inference is offline", file=sys.stderr, flush=True)
|
| 165 |
+
result = run_inference(
|
| 166 |
+
RunOptions(
|
| 167 |
+
input_path=input_path,
|
| 168 |
+
output_path=output,
|
| 169 |
+
model_dir=args.model_dir,
|
| 170 |
+
mode=args.mode,
|
| 171 |
+
max_length=args.max_length,
|
| 172 |
+
dpi=args.dpi,
|
| 173 |
+
start_page=args.start_page,
|
| 174 |
+
max_pages=args.max_pages,
|
| 175 |
+
max_page_pixels=args.max_page_pixels,
|
| 176 |
+
max_total_pixels=args.max_total_pixels,
|
| 177 |
+
max_page_rendered_bytes=args.max_page_rendered_mib * 1024**2,
|
| 178 |
+
max_rendered_bytes=args.max_rendered_mib * 1024**2,
|
| 179 |
+
prompt=args.prompt,
|
| 180 |
+
force=args.force,
|
| 181 |
+
quiet=args.quiet,
|
| 182 |
+
),
|
| 183 |
+
runtime,
|
| 184 |
+
)
|
| 185 |
+
for warning in result.warnings:
|
| 186 |
+
print(f"warning: {warning}", file=sys.stderr)
|
| 187 |
+
payload = {"status": "ok", **result.to_dict()}
|
| 188 |
+
_emit(payload, as_json=args.json, quiet=args.quiet)
|
| 189 |
+
return 0
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def run(argv: list[str] | None = None) -> int:
|
| 193 |
+
parser = build_parser()
|
| 194 |
+
args = parser.parse_args(argv)
|
| 195 |
+
if not args.command:
|
| 196 |
+
parser.print_help(sys.stderr)
|
| 197 |
+
return 2
|
| 198 |
+
try:
|
| 199 |
+
if args.command == "prepare":
|
| 200 |
+
return _command_prepare(args)
|
| 201 |
+
if args.command == "doctor":
|
| 202 |
+
return _command_doctor(args)
|
| 203 |
+
if args.command == "run":
|
| 204 |
+
return _command_run(args)
|
| 205 |
+
parser.error(f"unknown command: {args.command}")
|
| 206 |
+
except KeyboardInterrupt:
|
| 207 |
+
print("interrupted", file=sys.stderr)
|
| 208 |
+
return 130
|
| 209 |
+
except AppError as exc:
|
| 210 |
+
if args.json:
|
| 211 |
+
print(
|
| 212 |
+
json.dumps({"schema_version": 1, "status": "error", "error": str(exc), "exit_code": exc.exit_code}),
|
| 213 |
+
file=sys.stderr,
|
| 214 |
+
)
|
| 215 |
+
else:
|
| 216 |
+
print(f"error: {exc}", file=sys.stderr)
|
| 217 |
+
return exc.exit_code
|
| 218 |
+
except Exception as exc:
|
| 219 |
+
if args.debug:
|
| 220 |
+
traceback.print_exc()
|
| 221 |
+
else:
|
| 222 |
+
print(f"error: unexpected failure: {exc}; rerun with --debug", file=sys.stderr)
|
| 223 |
+
return 1
|
| 224 |
+
return 1
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def main() -> None:
|
| 228 |
+
raise SystemExit(run())
|
src/unlimited_ocr_rdna4/constants.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
MODEL_REPO = "baidu/Unlimited-OCR"
|
| 4 |
+
MODEL_REVISION = "07dea832e22aefee32ad281d4b80551282e1c168"
|
| 5 |
+
MODEL_WEIGHT_FILE = "model-00001-of-000001.safetensors"
|
| 6 |
+
MODEL_WEIGHT_BYTES = 6_672_547_120
|
| 7 |
+
MODEL_WEIGHT_SHA256 = "2bc48a7a110061ea58fff65d3169367eebe3aee371ca6968dc2219c1b2855fc6"
|
| 8 |
+
UPSTREAM_MODEL_CODE_SHA256 = "268bdcbe12cf37bf5a2debb53faf542e56570958a5d9f3314aab3cab2cf6cb48"
|
| 9 |
+
PATCHED_MODEL_CODE_SHA256 = "67d722bc082cf2524948ac75ba1cf014fa0bffe2bfbd2e59a9b8511d3654366c"
|
| 10 |
+
|
| 11 |
+
MODEL_MANIFEST_FILE = "rdna4-manifest.json"
|
| 12 |
+
MODEL_UPSTREAM_BACKUP_FILE = "modeling_unlimitedocr.py.upstream"
|
| 13 |
+
|
| 14 |
+
# Every behavior-defining file loaded by Transformers is pinned. The weight is
|
| 15 |
+
# listed separately because callers may choose whether to return its full hash,
|
| 16 |
+
# but verification always hashes it before trust_remote_code=True is used.
|
| 17 |
+
MODEL_PAYLOAD_SHA256 = {
|
| 18 |
+
"LICENSE": "d985048c6d69429d685fdbe7557340aa0897c0fd8dc038299b148b9c75dc3383",
|
| 19 |
+
"README.md": "4f573db255db5262bfe2ea92e459bfc11635a456f210e3a96289bb9605077a55",
|
| 20 |
+
"config.json": "27246d03fd670904ec9601b1cb0861fbb79ec076830771daa8d943d6229946f9",
|
| 21 |
+
"configuration_deepseek_v2.py": "b8470dd616ba8745fce6e27b093aef73a098863cc891b2477dcf9326a36000f7",
|
| 22 |
+
"conversation.py": "ec7b6ce89bcda643de1f43269ffa66a7b2e65dc3ed30e427958f776546b4ba03",
|
| 23 |
+
"deepencoder.py": "0ae2fb6d1e5ae8cf100fc32f854830acd08c821a0a1f23a94a76588c222ddcf2",
|
| 24 |
+
MODEL_WEIGHT_FILE: MODEL_WEIGHT_SHA256,
|
| 25 |
+
"model.safetensors.index.json": "354be1f2dcfb72ebb385e25465522ce5413a77c36f3b35fec088a3162a11af99",
|
| 26 |
+
"modeling_deepseekv2.py": "74e36e6bd0ba7bc565ef76464a99baa8e6bccb710ae9c1007b54ac30b855fa4c",
|
| 27 |
+
"modeling_unlimitedocr.py": PATCHED_MODEL_CODE_SHA256,
|
| 28 |
+
MODEL_UPSTREAM_BACKUP_FILE: UPSTREAM_MODEL_CODE_SHA256,
|
| 29 |
+
"processor_config.json": "92588cffb1d7032ec83d0a06c3a5171b41df5cbf432d68765441139a57899328",
|
| 30 |
+
"special_tokens_map.json": "ab4bd57ce17d62e39e0a39e739de1e407484f090f0b2c7e391312bca7a5b061a",
|
| 31 |
+
"tokenizer.json": "a02f8fd5228c90256bb4f6554c34a579d48f909e5beb232dc4afad870b55a8b4",
|
| 32 |
+
"tokenizer_config.json": "a0cbe8464049da1f891b7a12676de06af4cb54c130995d42f71adc1c30c6e9f3",
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
MODEL_DOWNLOAD_SHA256 = {
|
| 36 |
+
**MODEL_PAYLOAD_SHA256,
|
| 37 |
+
"modeling_unlimitedocr.py": UPSTREAM_MODEL_CODE_SHA256,
|
| 38 |
+
}
|
| 39 |
+
MODEL_DOWNLOAD_SHA256.pop(MODEL_UPSTREAM_BACKUP_FILE)
|
| 40 |
+
|
| 41 |
+
SUPPORTED_ARCHITECTURES = frozenset({"gfx1200", "gfx1201"})
|
| 42 |
+
HARDWARE_VERIFIED_ARCHITECTURES = frozenset({"gfx1201"})
|
| 43 |
+
VERIFIED_TORCH_VERSION = "2.9.1+rocm7.2.1.gitff65f5bc"
|
| 44 |
+
VERIFIED_HIP_VERSION = "7.2.53211-e1a6bc5663"
|
| 45 |
+
VERIFIED_ROCM_VERSION = "7.2.1"
|
| 46 |
+
VERIFIED_PACKAGE_VERSIONS = {
|
| 47 |
+
"numpy": "1.26.4",
|
| 48 |
+
"pillow": "12.1.1",
|
| 49 |
+
"pypdfium2": "5.12.1",
|
| 50 |
+
"torch": "2.9.1+rocm7.2.1.lw.gitff65f5bc",
|
| 51 |
+
"torchvision": "0.24.0+rocm7.2.1.gitb919bd0c",
|
| 52 |
+
"transformers": "4.57.1",
|
| 53 |
+
"triton": "3.5.1+rocm7.2.1.gita272dfa8",
|
| 54 |
+
}
|
| 55 |
+
VERSION = "0.1.0"
|
src/unlimited_ocr_rdna4/errors.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class AppError(Exception):
|
| 5 |
+
exit_code = 1
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class RuntimeEnvironmentError(AppError):
|
| 9 |
+
exit_code = 3
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class ModelIntegrityError(AppError):
|
| 13 |
+
exit_code = 4
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class InferenceError(AppError):
|
| 17 |
+
exit_code = 5
|
src/unlimited_ocr_rdna4/infer.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import contextlib
|
| 4 |
+
import errno
|
| 5 |
+
import os
|
| 6 |
+
import stat
|
| 7 |
+
import sys
|
| 8 |
+
import tempfile
|
| 9 |
+
import time
|
| 10 |
+
from dataclasses import asdict, dataclass
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from typing import Any, TextIO
|
| 13 |
+
|
| 14 |
+
from .constants import MODEL_REVISION
|
| 15 |
+
from .errors import InferenceError
|
| 16 |
+
from .model_store import verify_model
|
| 17 |
+
from .paths import default_cache_dir
|
| 18 |
+
from .pdf import PDFPageRenderer
|
| 19 |
+
from .postprocess import clean_model_output, repetition_warning
|
| 20 |
+
from .runtime import RuntimeInfo
|
| 21 |
+
|
| 22 |
+
SUPPORTED_IMAGE_SUFFIXES = frozenset({".bmp", ".jpeg", ".jpg", ".png", ".tif", ".tiff", ".webp"})
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@dataclass(frozen=True)
|
| 26 |
+
class RunOptions:
|
| 27 |
+
input_path: Path
|
| 28 |
+
output_path: Path
|
| 29 |
+
model_dir: Path
|
| 30 |
+
mode: str = "gundam"
|
| 31 |
+
max_length: int = 4096
|
| 32 |
+
dpi: int = 200
|
| 33 |
+
start_page: int = 1
|
| 34 |
+
max_pages: int = 20
|
| 35 |
+
max_page_pixels: int = 60_000_000
|
| 36 |
+
max_total_pixels: int = 400_000_000
|
| 37 |
+
max_page_rendered_bytes: int = 512 * 1024**2
|
| 38 |
+
max_rendered_bytes: int = 2 * 1024**3
|
| 39 |
+
prompt: str = "<image>document parsing."
|
| 40 |
+
force: bool = False
|
| 41 |
+
quiet: bool = False
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@dataclass(frozen=True)
|
| 45 |
+
class RunResult:
|
| 46 |
+
schema_version: int
|
| 47 |
+
input: str
|
| 48 |
+
output: str
|
| 49 |
+
model_revision: str
|
| 50 |
+
pages_processed: int
|
| 51 |
+
pages_total: int
|
| 52 |
+
mode: str
|
| 53 |
+
model_load_seconds: float
|
| 54 |
+
inference_seconds: float
|
| 55 |
+
page_seconds: tuple[float, ...]
|
| 56 |
+
peak_allocated_gib: float
|
| 57 |
+
torch_version: str
|
| 58 |
+
hip_version: str | None
|
| 59 |
+
gpu: str
|
| 60 |
+
architecture: str | None
|
| 61 |
+
hardware_verified: bool
|
| 62 |
+
software_verified: bool
|
| 63 |
+
warnings: tuple[str, ...]
|
| 64 |
+
|
| 65 |
+
def to_dict(self) -> dict[str, Any]:
|
| 66 |
+
return asdict(self)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def default_output_path(input_path: Path) -> Path:
|
| 70 |
+
return input_path.with_name(f"{input_path.name}.ocr.md")
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def _log_stream(quiet: bool) -> contextlib.AbstractContextManager[TextIO]:
|
| 74 |
+
if quiet:
|
| 75 |
+
return open(os.devnull, "w", encoding="utf-8")
|
| 76 |
+
return contextlib.nullcontext(sys.stderr)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def _load_model(model_dir: Path, log: TextIO):
|
| 80 |
+
import torch
|
| 81 |
+
from transformers import AutoModel, AutoTokenizer
|
| 82 |
+
|
| 83 |
+
with contextlib.redirect_stdout(log):
|
| 84 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 85 |
+
model_dir,
|
| 86 |
+
trust_remote_code=True,
|
| 87 |
+
local_files_only=True,
|
| 88 |
+
)
|
| 89 |
+
model = (
|
| 90 |
+
AutoModel.from_pretrained(
|
| 91 |
+
model_dir,
|
| 92 |
+
trust_remote_code=True,
|
| 93 |
+
local_files_only=True,
|
| 94 |
+
use_safetensors=True,
|
| 95 |
+
dtype=torch.bfloat16,
|
| 96 |
+
)
|
| 97 |
+
.eval()
|
| 98 |
+
.cuda()
|
| 99 |
+
)
|
| 100 |
+
torch.cuda.synchronize()
|
| 101 |
+
return model, tokenizer
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _validated_output_path(input_path: Path, requested: Path, *, force: bool) -> Path:
|
| 105 |
+
unresolved = requested.expanduser().absolute()
|
| 106 |
+
unresolved.parent.mkdir(parents=True, exist_ok=True)
|
| 107 |
+
output_path = unresolved.parent.resolve(strict=True) / unresolved.name
|
| 108 |
+
if output_path == input_path:
|
| 109 |
+
raise InferenceError("input and output must be different files")
|
| 110 |
+
if output_path.is_symlink():
|
| 111 |
+
raise InferenceError(f"output must not be a symlink: {output_path}")
|
| 112 |
+
if output_path.exists():
|
| 113 |
+
if not stat.S_ISREG(output_path.lstat().st_mode):
|
| 114 |
+
raise InferenceError(f"output exists and is not a regular file: {output_path}")
|
| 115 |
+
if os.path.samefile(input_path, output_path):
|
| 116 |
+
raise InferenceError("input and output resolve to the same file")
|
| 117 |
+
if not force:
|
| 118 |
+
raise InferenceError(f"output already exists: {output_path}; pass --force to replace it")
|
| 119 |
+
return output_path
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def _publish_text(output_path: Path, content: str, *, force: bool) -> None:
|
| 123 |
+
descriptor, temporary_name = tempfile.mkstemp(prefix=f".{output_path.name}.", suffix=".tmp", dir=output_path.parent)
|
| 124 |
+
temporary = Path(temporary_name)
|
| 125 |
+
try:
|
| 126 |
+
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
|
| 127 |
+
handle.write(content.rstrip() + "\n")
|
| 128 |
+
handle.flush()
|
| 129 |
+
os.fsync(handle.fileno())
|
| 130 |
+
if force:
|
| 131 |
+
os.replace(temporary, output_path)
|
| 132 |
+
else:
|
| 133 |
+
try:
|
| 134 |
+
os.link(temporary, output_path, follow_symlinks=False)
|
| 135 |
+
except FileExistsError as exc:
|
| 136 |
+
raise InferenceError(
|
| 137 |
+
f"output was created while inference was running: {output_path}; pass --force to replace it"
|
| 138 |
+
) from exc
|
| 139 |
+
temporary.unlink()
|
| 140 |
+
directory_fd = os.open(output_path.parent, os.O_RDONLY | os.O_DIRECTORY)
|
| 141 |
+
try:
|
| 142 |
+
os.fsync(directory_fd)
|
| 143 |
+
finally:
|
| 144 |
+
os.close(directory_fd)
|
| 145 |
+
except OSError as exc:
|
| 146 |
+
if isinstance(exc, InferenceError):
|
| 147 |
+
raise
|
| 148 |
+
if exc.errno in {errno.EISDIR, errno.ENOTDIR}:
|
| 149 |
+
raise InferenceError(f"output path changed while inference was running: {output_path}") from exc
|
| 150 |
+
raise
|
| 151 |
+
finally:
|
| 152 |
+
temporary.unlink(missing_ok=True)
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def run_inference(options: RunOptions, runtime: RuntimeInfo) -> RunResult:
|
| 156 |
+
input_path = options.input_path.expanduser().resolve(strict=True)
|
| 157 |
+
if not stat.S_ISREG(input_path.stat().st_mode):
|
| 158 |
+
raise InferenceError(f"input must be a regular file: {input_path}")
|
| 159 |
+
output_path = _validated_output_path(input_path, options.output_path, force=options.force)
|
| 160 |
+
model_dir = options.model_dir.expanduser().resolve()
|
| 161 |
+
|
| 162 |
+
suffix = input_path.suffix.lower()
|
| 163 |
+
if suffix != ".pdf" and suffix not in SUPPORTED_IMAGE_SUFFIXES:
|
| 164 |
+
supported = ", ".join(sorted(SUPPORTED_IMAGE_SUFFIXES | {".pdf"}))
|
| 165 |
+
raise InferenceError(f"unsupported input extension {suffix or '(none)'}; supported: {supported}")
|
| 166 |
+
if options.start_page < 1:
|
| 167 |
+
raise InferenceError("--start-page must be at least 1")
|
| 168 |
+
|
| 169 |
+
verify_model(model_dir)
|
| 170 |
+
cache_dir = default_cache_dir()
|
| 171 |
+
work_root = cache_dir / "tmp"
|
| 172 |
+
work_root.mkdir(parents=True, exist_ok=True)
|
| 173 |
+
os.environ["HF_HOME"] = str(cache_dir / "huggingface")
|
| 174 |
+
os.environ["HF_HUB_OFFLINE"] = "1"
|
| 175 |
+
os.environ["TRANSFORMERS_OFFLINE"] = "1"
|
| 176 |
+
|
| 177 |
+
import torch
|
| 178 |
+
|
| 179 |
+
with _log_stream(options.quiet) as log:
|
| 180 |
+
torch.cuda.reset_peak_memory_stats()
|
| 181 |
+
load_started = time.monotonic()
|
| 182 |
+
model, tokenizer = _load_model(model_dir, log)
|
| 183 |
+
model_load_seconds = time.monotonic() - load_started
|
| 184 |
+
|
| 185 |
+
if options.mode == "gundam":
|
| 186 |
+
base_size, image_size, crop_mode = 1024, 640, True
|
| 187 |
+
else:
|
| 188 |
+
base_size, image_size, crop_mode = 1024, 1024, False
|
| 189 |
+
|
| 190 |
+
page_seconds: list[float] = []
|
| 191 |
+
warnings: list[str] = []
|
| 192 |
+
|
| 193 |
+
def infer_image(image_path: Path) -> str:
|
| 194 |
+
started = time.monotonic()
|
| 195 |
+
try:
|
| 196 |
+
with contextlib.redirect_stdout(log), torch.inference_mode():
|
| 197 |
+
text = model.infer(
|
| 198 |
+
tokenizer,
|
| 199 |
+
prompt=options.prompt,
|
| 200 |
+
image_file=str(image_path),
|
| 201 |
+
output_path=str(work_dir),
|
| 202 |
+
base_size=base_size,
|
| 203 |
+
image_size=image_size,
|
| 204 |
+
crop_mode=crop_mode,
|
| 205 |
+
max_length=options.max_length,
|
| 206 |
+
no_repeat_ngram_size=35,
|
| 207 |
+
ngram_window=128,
|
| 208 |
+
temperature=0.0,
|
| 209 |
+
save_results=False,
|
| 210 |
+
eval_mode=True,
|
| 211 |
+
)
|
| 212 |
+
torch.cuda.synchronize()
|
| 213 |
+
except ValueError as exc:
|
| 214 |
+
if "max_length" in str(exc) and "Input length" in str(exc):
|
| 215 |
+
raise InferenceError(
|
| 216 |
+
f"{exc} Increase --max-length; 4096 is the tested single-page default."
|
| 217 |
+
) from exc
|
| 218 |
+
raise InferenceError(f"generation failed: {exc}") from exc
|
| 219 |
+
except torch.OutOfMemoryError as exc:
|
| 220 |
+
raise InferenceError(
|
| 221 |
+
"GPU out of memory. Stop other GPU workloads, use --mode gundam, or reduce PDF page scope."
|
| 222 |
+
) from exc
|
| 223 |
+
elapsed = time.monotonic() - started
|
| 224 |
+
page_seconds.append(elapsed)
|
| 225 |
+
cleaned = clean_model_output(text)
|
| 226 |
+
warning = repetition_warning(cleaned)
|
| 227 |
+
if warning:
|
| 228 |
+
warnings.append(f"{image_path.name}: {warning}")
|
| 229 |
+
return cleaned
|
| 230 |
+
|
| 231 |
+
with tempfile.TemporaryDirectory(prefix="run-", dir=work_root) as temporary:
|
| 232 |
+
work_dir = Path(temporary)
|
| 233 |
+
inference_started = time.monotonic()
|
| 234 |
+
if suffix == ".pdf":
|
| 235 |
+
with PDFPageRenderer(
|
| 236 |
+
input_path,
|
| 237 |
+
work_dir,
|
| 238 |
+
dpi=options.dpi,
|
| 239 |
+
start_page=options.start_page,
|
| 240 |
+
max_pages=options.max_pages,
|
| 241 |
+
max_page_pixels=options.max_page_pixels,
|
| 242 |
+
max_total_pixels=options.max_total_pixels,
|
| 243 |
+
max_page_rendered_bytes=options.max_page_rendered_bytes,
|
| 244 |
+
max_rendered_bytes=options.max_rendered_bytes,
|
| 245 |
+
) as renderer:
|
| 246 |
+
sections = []
|
| 247 |
+
for page_number in renderer.page_numbers:
|
| 248 |
+
image_path = renderer.render(page_number)
|
| 249 |
+
try:
|
| 250 |
+
sections.append(f"<!-- page {page_number} -->\n\n{infer_image(image_path)}")
|
| 251 |
+
finally:
|
| 252 |
+
image_path.unlink(missing_ok=True)
|
| 253 |
+
pages_processed = len(sections)
|
| 254 |
+
pages_total = renderer.total_pages
|
| 255 |
+
content = "\n\n---\n\n".join(sections)
|
| 256 |
+
if options.start_page - 1 + pages_processed < pages_total:
|
| 257 |
+
warnings.append(
|
| 258 |
+
f"processed {pages_processed} page(s) starting at {options.start_page}; "
|
| 259 |
+
f"PDF contains {pages_total} page(s)"
|
| 260 |
+
)
|
| 261 |
+
else:
|
| 262 |
+
content = infer_image(input_path)
|
| 263 |
+
pages_processed = pages_total = 1
|
| 264 |
+
inference_seconds = time.monotonic() - inference_started
|
| 265 |
+
|
| 266 |
+
_publish_text(output_path, content, force=options.force)
|
| 267 |
+
|
| 268 |
+
device = runtime.devices[0]
|
| 269 |
+
return RunResult(
|
| 270 |
+
schema_version=1,
|
| 271 |
+
input=str(input_path),
|
| 272 |
+
output=str(output_path),
|
| 273 |
+
model_revision=MODEL_REVISION,
|
| 274 |
+
pages_processed=pages_processed,
|
| 275 |
+
pages_total=pages_total,
|
| 276 |
+
mode=options.mode,
|
| 277 |
+
model_load_seconds=round(model_load_seconds, 3),
|
| 278 |
+
inference_seconds=round(inference_seconds, 3),
|
| 279 |
+
page_seconds=tuple(round(value, 3) for value in page_seconds),
|
| 280 |
+
peak_allocated_gib=round(torch.cuda.max_memory_allocated() / (1024**3), 3),
|
| 281 |
+
torch_version=runtime.torch_version,
|
| 282 |
+
hip_version=runtime.hip_version,
|
| 283 |
+
gpu=device.name,
|
| 284 |
+
architecture=device.architecture,
|
| 285 |
+
hardware_verified=runtime.hardware_verified,
|
| 286 |
+
software_verified=runtime.software_verified,
|
| 287 |
+
warnings=tuple([*runtime.warnings, *warnings]),
|
| 288 |
+
)
|
src/unlimited_ocr_rdna4/model_store.py
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import hashlib
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
import shutil
|
| 7 |
+
import stat
|
| 8 |
+
import tempfile
|
| 9 |
+
from dataclasses import asdict, dataclass
|
| 10 |
+
from fcntl import LOCK_EX, flock
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from typing import BinaryIO
|
| 13 |
+
|
| 14 |
+
from .constants import (
|
| 15 |
+
MODEL_DOWNLOAD_SHA256,
|
| 16 |
+
MODEL_MANIFEST_FILE,
|
| 17 |
+
MODEL_PAYLOAD_SHA256,
|
| 18 |
+
MODEL_REPO,
|
| 19 |
+
MODEL_REVISION,
|
| 20 |
+
MODEL_UPSTREAM_BACKUP_FILE,
|
| 21 |
+
MODEL_WEIGHT_BYTES,
|
| 22 |
+
MODEL_WEIGHT_FILE,
|
| 23 |
+
MODEL_WEIGHT_SHA256,
|
| 24 |
+
PATCHED_MODEL_CODE_SHA256,
|
| 25 |
+
UPSTREAM_MODEL_CODE_SHA256,
|
| 26 |
+
VERIFIED_ROCM_VERSION,
|
| 27 |
+
VERIFIED_TORCH_VERSION,
|
| 28 |
+
)
|
| 29 |
+
from .errors import ModelIntegrityError
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
@dataclass(frozen=True)
|
| 33 |
+
class ModelStatus:
|
| 34 |
+
model_dir: str
|
| 35 |
+
revision: str
|
| 36 |
+
prepared: bool
|
| 37 |
+
weight_bytes: int
|
| 38 |
+
weight_sha256: str | None
|
| 39 |
+
model_code_sha256: str
|
| 40 |
+
reused: bool = False
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def _regular_file(path: Path) -> bool:
|
| 44 |
+
try:
|
| 45 |
+
return stat.S_ISREG(path.lstat().st_mode)
|
| 46 |
+
except FileNotFoundError:
|
| 47 |
+
return False
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def sha256_file(path: Path) -> str:
|
| 51 |
+
digest = hashlib.sha256()
|
| 52 |
+
with path.open("rb") as handle:
|
| 53 |
+
for chunk in iter(lambda: handle.read(4 * 1024 * 1024), b""):
|
| 54 |
+
digest.update(chunk)
|
| 55 |
+
return digest.hexdigest()
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def sha256_text(text: str) -> str:
|
| 59 |
+
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _replace_exact(text: str, old: str, new: str, expected: int) -> str:
|
| 63 |
+
found = text.count(old)
|
| 64 |
+
if found != expected:
|
| 65 |
+
raise ModelIntegrityError(f"model patch context mismatch: expected {expected} occurrence(s), found {found}")
|
| 66 |
+
return text.replace(old, new)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def patch_model_source_text(text: str) -> str:
|
| 70 |
+
current_hash = sha256_text(text)
|
| 71 |
+
if current_hash == PATCHED_MODEL_CODE_SHA256:
|
| 72 |
+
return text
|
| 73 |
+
if current_hash != UPSTREAM_MODEL_CODE_SHA256:
|
| 74 |
+
raise ModelIntegrityError(
|
| 75 |
+
"refusing to patch unknown model code; expected pinned Baidu revision "
|
| 76 |
+
f"{MODEL_REVISION}, got SHA-256 {current_hash}"
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
text = _replace_exact(
|
| 80 |
+
text,
|
| 81 |
+
"from .modeling_deepseekv2 import DeepseekV2Model, DeepseekV2ForCausalLM",
|
| 82 |
+
"import ast\n\nfrom .modeling_deepseekv2 import DeepseekV2Model, DeepseekV2ForCausalLM",
|
| 83 |
+
1,
|
| 84 |
+
)
|
| 85 |
+
replacements = (
|
| 86 |
+
("cor_list = eval(ref_text[2])", "cor_list = ast.literal_eval(ref_text[2])"),
|
| 87 |
+
("lines = eval(outputs)['Line']['line']", "lines = ast.literal_eval(outputs)['Line']['line']"),
|
| 88 |
+
(
|
| 89 |
+
"line_type = eval(outputs)['Line']['line_type']",
|
| 90 |
+
"line_type = ast.literal_eval(outputs)['Line']['line_type']",
|
| 91 |
+
),
|
| 92 |
+
(
|
| 93 |
+
"endpoints = eval(outputs)['Line']['line_endpoint']",
|
| 94 |
+
"endpoints = ast.literal_eval(outputs)['Line']['line_endpoint']",
|
| 95 |
+
),
|
| 96 |
+
("p0 = eval(line.split(' -- ')[0])", "p0 = ast.literal_eval(line.split(' -- ')[0])"),
|
| 97 |
+
("p1 = eval(line.split(' -- ')[-1])", "p1 = ast.literal_eval(line.split(' -- ')[-1])"),
|
| 98 |
+
("(x, y) = eval(endpoint.split(': ')[1])", "(x, y) = ast.literal_eval(endpoint.split(': ')[1])"),
|
| 99 |
+
)
|
| 100 |
+
for old, new in replacements:
|
| 101 |
+
text = _replace_exact(text, old, new, 1)
|
| 102 |
+
|
| 103 |
+
text = _replace_exact(
|
| 104 |
+
text,
|
| 105 |
+
"images_seq_mask[idx].unsqueeze(-1).cuda()",
|
| 106 |
+
"images_seq_mask[idx].unsqueeze(-1).to(inputs_embeds.device)",
|
| 107 |
+
1,
|
| 108 |
+
)
|
| 109 |
+
text = _replace_exact(
|
| 110 |
+
text,
|
| 111 |
+
" input_ids=input_ids.unsqueeze(0).cuda(),\n",
|
| 112 |
+
" input_ids=input_ids.unsqueeze(0).cuda(),\n"
|
| 113 |
+
" attention_mask=torch.ones_like(input_ids.unsqueeze(0), device='cuda'),\n",
|
| 114 |
+
3,
|
| 115 |
+
)
|
| 116 |
+
text = _replace_exact(
|
| 117 |
+
text,
|
| 118 |
+
" eos_token_id=tokenizer.eos_token_id,\n",
|
| 119 |
+
" eos_token_id=tokenizer.eos_token_id,\n pad_token_id=tokenizer.eos_token_id,\n",
|
| 120 |
+
3,
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
patched_hash = sha256_text(text)
|
| 124 |
+
if PATCHED_MODEL_CODE_SHA256 != "__TO_BE_FILLED__" and patched_hash != PATCHED_MODEL_CODE_SHA256:
|
| 125 |
+
raise ModelIntegrityError(f"patched model code hash mismatch: {patched_hash}")
|
| 126 |
+
return text
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def patch_model_file(model_dir: Path) -> str:
|
| 130 |
+
source = model_dir / "modeling_unlimitedocr.py"
|
| 131 |
+
text = source.read_text(encoding="utf-8")
|
| 132 |
+
patched = patch_model_source_text(text)
|
| 133 |
+
backup = model_dir / MODEL_UPSTREAM_BACKUP_FILE
|
| 134 |
+
if not backup.exists() and sha256_text(text) == UPSTREAM_MODEL_CODE_SHA256:
|
| 135 |
+
shutil.copy2(source, backup)
|
| 136 |
+
temporary = source.with_suffix(".py.new")
|
| 137 |
+
temporary.write_text(patched, encoding="utf-8")
|
| 138 |
+
os.replace(temporary, source)
|
| 139 |
+
return sha256_text(patched)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def _manifest_payload() -> dict[str, object]:
|
| 143 |
+
return {
|
| 144 |
+
"schema_version": 1,
|
| 145 |
+
"source": MODEL_REPO,
|
| 146 |
+
"revision": MODEL_REVISION,
|
| 147 |
+
"files": dict(sorted(MODEL_PAYLOAD_SHA256.items())),
|
| 148 |
+
"verified_rocm": VERIFIED_ROCM_VERSION,
|
| 149 |
+
"verified_torch": VERIFIED_TORCH_VERSION,
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def _write_manifest(model_dir: Path) -> None:
|
| 154 |
+
manifest = model_dir / MODEL_MANIFEST_FILE
|
| 155 |
+
descriptor, temporary_name = tempfile.mkstemp(prefix=f".{MODEL_MANIFEST_FILE}.", dir=model_dir)
|
| 156 |
+
try:
|
| 157 |
+
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
|
| 158 |
+
json.dump(_manifest_payload(), handle, indent=2, sort_keys=True)
|
| 159 |
+
handle.write("\n")
|
| 160 |
+
handle.flush()
|
| 161 |
+
os.fsync(handle.fileno())
|
| 162 |
+
os.replace(temporary_name, manifest)
|
| 163 |
+
finally:
|
| 164 |
+
Path(temporary_name).unlink(missing_ok=True)
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def _verify_payload(model_dir: Path, expected_hashes: dict[str, str]) -> dict[str, str]:
|
| 168 |
+
if model_dir.is_symlink() or not model_dir.is_dir():
|
| 169 |
+
raise ModelIntegrityError(f"model path must be a real directory, not a symlink: {model_dir}")
|
| 170 |
+
|
| 171 |
+
missing = [name for name in expected_hashes if not _regular_file(model_dir / name)]
|
| 172 |
+
if missing:
|
| 173 |
+
raise ModelIntegrityError(f"model is incomplete at {model_dir}; missing regular files: {', '.join(missing)}")
|
| 174 |
+
|
| 175 |
+
weight = model_dir / MODEL_WEIGHT_FILE
|
| 176 |
+
weight_bytes = weight.stat().st_size
|
| 177 |
+
if weight_bytes != MODEL_WEIGHT_BYTES:
|
| 178 |
+
raise ModelIntegrityError(f"weight size mismatch: expected {MODEL_WEIGHT_BYTES}, got {weight_bytes}")
|
| 179 |
+
|
| 180 |
+
actual_hashes: dict[str, str] = {}
|
| 181 |
+
for name, expected in expected_hashes.items():
|
| 182 |
+
actual = sha256_file(model_dir / name)
|
| 183 |
+
if actual != expected:
|
| 184 |
+
raise ModelIntegrityError(f"model file SHA-256 mismatch for {name}: expected {expected}, got {actual}")
|
| 185 |
+
actual_hashes[name] = actual
|
| 186 |
+
return actual_hashes
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def _verify_exact_layout(model_dir: Path) -> None:
|
| 190 |
+
if model_dir.is_symlink() or not model_dir.is_dir():
|
| 191 |
+
raise ModelIntegrityError(f"model path must be a real directory, not a symlink: {model_dir}")
|
| 192 |
+
expected = set(MODEL_PAYLOAD_SHA256) | {MODEL_MANIFEST_FILE}
|
| 193 |
+
actual = {entry.name for entry in model_dir.iterdir()}
|
| 194 |
+
unexpected = sorted(actual - expected)
|
| 195 |
+
missing = sorted(expected - actual)
|
| 196 |
+
if unexpected or missing:
|
| 197 |
+
details = []
|
| 198 |
+
if missing:
|
| 199 |
+
details.append(f"missing: {', '.join(missing)}")
|
| 200 |
+
if unexpected:
|
| 201 |
+
details.append(f"unexpected: {', '.join(unexpected)}")
|
| 202 |
+
raise ModelIntegrityError(f"model layout mismatch at {model_dir}; {'; '.join(details)}")
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def verify_model(model_dir: Path, *, full_weight_hash: bool = True) -> ModelStatus:
|
| 206 |
+
del full_weight_hash # Kept for API compatibility; security verification is always complete.
|
| 207 |
+
_verify_exact_layout(model_dir)
|
| 208 |
+
hashes = _verify_payload(model_dir, MODEL_PAYLOAD_SHA256)
|
| 209 |
+
|
| 210 |
+
manifest_path = model_dir / MODEL_MANIFEST_FILE
|
| 211 |
+
if not _regular_file(manifest_path):
|
| 212 |
+
raise ModelIntegrityError(f"model manifest is missing or not a regular file: {manifest_path}")
|
| 213 |
+
try:
|
| 214 |
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
| 215 |
+
except (OSError, json.JSONDecodeError) as exc:
|
| 216 |
+
raise ModelIntegrityError(f"invalid model manifest at {manifest_path}: {exc}") from exc
|
| 217 |
+
if manifest != _manifest_payload():
|
| 218 |
+
raise ModelIntegrityError(f"model manifest does not match the pinned runtime contract: {manifest_path}")
|
| 219 |
+
|
| 220 |
+
code_hash = hashes["modeling_unlimitedocr.py"]
|
| 221 |
+
weight_hash = hashes[MODEL_WEIGHT_FILE]
|
| 222 |
+
weight_bytes = (model_dir / MODEL_WEIGHT_FILE).stat().st_size
|
| 223 |
+
|
| 224 |
+
return ModelStatus(
|
| 225 |
+
model_dir=str(model_dir),
|
| 226 |
+
revision=MODEL_REVISION,
|
| 227 |
+
prepared=True,
|
| 228 |
+
weight_bytes=weight_bytes,
|
| 229 |
+
weight_sha256=weight_hash,
|
| 230 |
+
model_code_sha256=code_hash,
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def _remove_snapshot_metadata(model_dir: Path) -> None:
|
| 235 |
+
metadata = model_dir / ".cache"
|
| 236 |
+
if metadata.is_symlink():
|
| 237 |
+
raise ModelIntegrityError(f"refusing symlinked snapshot metadata: {metadata}")
|
| 238 |
+
if metadata.exists():
|
| 239 |
+
shutil.rmtree(metadata)
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def _upgrade_existing_model(model_dir: Path) -> ModelStatus:
|
| 243 |
+
allowed = set(MODEL_PAYLOAD_SHA256) | {MODEL_MANIFEST_FILE, ".cache"}
|
| 244 |
+
actual = {entry.name for entry in model_dir.iterdir()}
|
| 245 |
+
unexpected = sorted(actual - allowed)
|
| 246 |
+
if unexpected:
|
| 247 |
+
raise ModelIntegrityError(f"refusing unexpected entries in prepared model: {', '.join(unexpected)}")
|
| 248 |
+
_verify_payload(model_dir, MODEL_PAYLOAD_SHA256)
|
| 249 |
+
_remove_snapshot_metadata(model_dir)
|
| 250 |
+
_write_manifest(model_dir)
|
| 251 |
+
return verify_model(model_dir)
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def _lock_handle(path: Path) -> BinaryIO:
|
| 255 |
+
descriptor = os.open(path, os.O_RDWR | os.O_CREAT | os.O_CLOEXEC | os.O_NOFOLLOW, 0o600)
|
| 256 |
+
handle = os.fdopen(descriptor, "a+b")
|
| 257 |
+
flock(handle.fileno(), LOCK_EX)
|
| 258 |
+
return handle
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def prepare_model(model_dir: Path, cache_dir: Path, *, dry_run: bool = False) -> ModelStatus:
|
| 262 |
+
model_dir = model_dir.expanduser().absolute()
|
| 263 |
+
cache_dir = cache_dir.expanduser().resolve()
|
| 264 |
+
if dry_run:
|
| 265 |
+
return ModelStatus(
|
| 266 |
+
model_dir=str(model_dir),
|
| 267 |
+
revision=MODEL_REVISION,
|
| 268 |
+
prepared=False,
|
| 269 |
+
weight_bytes=MODEL_WEIGHT_BYTES,
|
| 270 |
+
weight_sha256=MODEL_WEIGHT_SHA256,
|
| 271 |
+
model_code_sha256=PATCHED_MODEL_CODE_SHA256,
|
| 272 |
+
)
|
| 273 |
+
|
| 274 |
+
if model_dir.is_symlink():
|
| 275 |
+
raise ModelIntegrityError(f"model destination must not be a symlink: {model_dir}")
|
| 276 |
+
model_dir.parent.mkdir(parents=True, exist_ok=True)
|
| 277 |
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
| 278 |
+
|
| 279 |
+
try:
|
| 280 |
+
from huggingface_hub import snapshot_download
|
| 281 |
+
except ImportError as exc:
|
| 282 |
+
raise ModelIntegrityError("huggingface-hub is missing; install the package dependencies") from exc
|
| 283 |
+
|
| 284 |
+
lock_path = model_dir.parent / f".{model_dir.name}.prepare.lock"
|
| 285 |
+
with _lock_handle(lock_path):
|
| 286 |
+
if model_dir.exists():
|
| 287 |
+
if not model_dir.is_dir():
|
| 288 |
+
raise ModelIntegrityError(f"model destination exists and is not a directory: {model_dir}")
|
| 289 |
+
status = _upgrade_existing_model(model_dir)
|
| 290 |
+
return ModelStatus(**{**asdict(status), "reused": True})
|
| 291 |
+
|
| 292 |
+
partial = Path(tempfile.mkdtemp(prefix=f".{model_dir.name}.partial-", dir=model_dir.parent))
|
| 293 |
+
try:
|
| 294 |
+
snapshot_download(
|
| 295 |
+
repo_id=MODEL_REPO,
|
| 296 |
+
revision=MODEL_REVISION,
|
| 297 |
+
local_dir=partial,
|
| 298 |
+
cache_dir=cache_dir / "huggingface",
|
| 299 |
+
allow_patterns=sorted(MODEL_DOWNLOAD_SHA256),
|
| 300 |
+
)
|
| 301 |
+
_verify_payload(partial, MODEL_DOWNLOAD_SHA256)
|
| 302 |
+
patch_model_file(partial)
|
| 303 |
+
_remove_snapshot_metadata(partial)
|
| 304 |
+
_write_manifest(partial)
|
| 305 |
+
status = verify_model(partial)
|
| 306 |
+
partial.rename(model_dir)
|
| 307 |
+
except Exception:
|
| 308 |
+
shutil.rmtree(partial, ignore_errors=True)
|
| 309 |
+
raise
|
| 310 |
+
return ModelStatus(**{**asdict(status), "model_dir": str(model_dir)})
|
src/unlimited_ocr_rdna4/paths.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
from .constants import MODEL_REVISION
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def data_home() -> Path:
|
| 10 |
+
value = os.environ.get("XDG_DATA_HOME")
|
| 11 |
+
return Path(value).expanduser() if value else Path.home() / ".local" / "share"
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def cache_home() -> Path:
|
| 15 |
+
value = os.environ.get("XDG_CACHE_HOME")
|
| 16 |
+
return Path(value).expanduser() if value else Path.home() / ".cache"
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def default_model_dir() -> Path:
|
| 20 |
+
override = os.environ.get("UNLIMITED_OCR_MODEL_DIR")
|
| 21 |
+
if override:
|
| 22 |
+
return Path(override).expanduser()
|
| 23 |
+
return data_home() / "unlimited-ocr-rdna4" / "models" / MODEL_REVISION
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def default_cache_dir() -> Path:
|
| 27 |
+
return cache_home() / "unlimited-ocr-rdna4"
|
src/unlimited_ocr_rdna4/pdf.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from types import ModuleType
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
from .errors import InferenceError
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class PDFPageRenderer:
|
| 12 |
+
"""Bounded, page-at-a-time PDFium renderer."""
|
| 13 |
+
|
| 14 |
+
def __init__(
|
| 15 |
+
self,
|
| 16 |
+
pdf_path: Path,
|
| 17 |
+
work_dir: Path,
|
| 18 |
+
*,
|
| 19 |
+
dpi: int,
|
| 20 |
+
start_page: int,
|
| 21 |
+
max_pages: int,
|
| 22 |
+
max_page_pixels: int,
|
| 23 |
+
max_total_pixels: int,
|
| 24 |
+
max_page_rendered_bytes: int,
|
| 25 |
+
max_rendered_bytes: int,
|
| 26 |
+
) -> None:
|
| 27 |
+
self.pdf_path = pdf_path
|
| 28 |
+
self.work_dir = work_dir
|
| 29 |
+
self.dpi = dpi
|
| 30 |
+
self.start_page = start_page
|
| 31 |
+
self.max_pages = max_pages
|
| 32 |
+
self.max_page_pixels = max_page_pixels
|
| 33 |
+
self.max_total_pixels = max_total_pixels
|
| 34 |
+
self.max_page_rendered_bytes = max_page_rendered_bytes
|
| 35 |
+
self.max_rendered_bytes = max_rendered_bytes
|
| 36 |
+
self._pdfium: ModuleType | None = None
|
| 37 |
+
self._document: Any = None
|
| 38 |
+
self.total_pages = 0
|
| 39 |
+
self._rendered_pixels = 0
|
| 40 |
+
self._rendered_bytes = 0
|
| 41 |
+
|
| 42 |
+
def __enter__(self) -> PDFPageRenderer:
|
| 43 |
+
try:
|
| 44 |
+
import pypdfium2 as pdfium
|
| 45 |
+
except ImportError as exc:
|
| 46 |
+
raise InferenceError("pypdfium2 is missing; reinstall unlimited-ocr-rdna4") from exc
|
| 47 |
+
|
| 48 |
+
self._pdfium = pdfium
|
| 49 |
+
try:
|
| 50 |
+
self._document = pdfium.PdfDocument(str(self.pdf_path))
|
| 51 |
+
self.total_pages = len(self._document)
|
| 52 |
+
except Exception as exc:
|
| 53 |
+
raise InferenceError(f"could not open PDF {self.pdf_path}: {exc}") from exc
|
| 54 |
+
|
| 55 |
+
if self.total_pages < 1:
|
| 56 |
+
self.close()
|
| 57 |
+
raise InferenceError(f"PDF has no pages: {self.pdf_path}")
|
| 58 |
+
if self.start_page > self.total_pages:
|
| 59 |
+
self.close()
|
| 60 |
+
raise InferenceError(f"start page {self.start_page} exceeds PDF page count {self.total_pages}")
|
| 61 |
+
return self
|
| 62 |
+
|
| 63 |
+
def __exit__(self, _exc_type: object, _exc: object, _traceback: object) -> None:
|
| 64 |
+
self.close()
|
| 65 |
+
|
| 66 |
+
@property
|
| 67 |
+
def page_numbers(self) -> range:
|
| 68 |
+
last_page = min(self.total_pages, self.start_page - 1 + self.max_pages)
|
| 69 |
+
return range(self.start_page, last_page + 1)
|
| 70 |
+
|
| 71 |
+
def render(self, page_number: int) -> Path:
|
| 72 |
+
if self._document is None:
|
| 73 |
+
raise InferenceError("PDF renderer is not open")
|
| 74 |
+
page = None
|
| 75 |
+
bitmap = None
|
| 76 |
+
image = None
|
| 77 |
+
output = self.work_dir / f"page-{page_number:06d}.png"
|
| 78 |
+
try:
|
| 79 |
+
page = self._document[page_number - 1]
|
| 80 |
+
width_points, height_points = page.get_size()
|
| 81 |
+
scale = self.dpi / 72
|
| 82 |
+
width_pixels = math.ceil(width_points * scale)
|
| 83 |
+
height_pixels = math.ceil(height_points * scale)
|
| 84 |
+
pixels = width_pixels * height_pixels
|
| 85 |
+
if pixels <= 0 or pixels > self.max_page_pixels:
|
| 86 |
+
raise InferenceError(
|
| 87 |
+
f"PDF page {page_number} would render {pixels:,} pixels; limit is {self.max_page_pixels:,}"
|
| 88 |
+
)
|
| 89 |
+
if self._rendered_pixels + pixels > self.max_total_pixels:
|
| 90 |
+
raise InferenceError(
|
| 91 |
+
f"PDF rendered-pixel total would exceed {self.max_total_pixels:,} on page {page_number}"
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
bitmap = page.render(scale=scale)
|
| 95 |
+
image = bitmap.to_pil()
|
| 96 |
+
image.save(output, format="PNG", optimize=True)
|
| 97 |
+
rendered_bytes = output.stat().st_size
|
| 98 |
+
if rendered_bytes > self.max_page_rendered_bytes:
|
| 99 |
+
output.unlink(missing_ok=True)
|
| 100 |
+
raise InferenceError(
|
| 101 |
+
f"PDF page {page_number} rendered to {rendered_bytes:,} bytes; "
|
| 102 |
+
f"limit is {self.max_page_rendered_bytes:,}"
|
| 103 |
+
)
|
| 104 |
+
if self._rendered_bytes + rendered_bytes > self.max_rendered_bytes:
|
| 105 |
+
output.unlink(missing_ok=True)
|
| 106 |
+
raise InferenceError(
|
| 107 |
+
f"PDF rendered-byte total would exceed {self.max_rendered_bytes:,} on page {page_number}"
|
| 108 |
+
)
|
| 109 |
+
self._rendered_pixels += pixels
|
| 110 |
+
self._rendered_bytes += rendered_bytes
|
| 111 |
+
return output
|
| 112 |
+
except InferenceError:
|
| 113 |
+
raise
|
| 114 |
+
except Exception as exc:
|
| 115 |
+
output.unlink(missing_ok=True)
|
| 116 |
+
raise InferenceError(f"could not render PDF page {page_number}: {exc}") from exc
|
| 117 |
+
finally:
|
| 118 |
+
if image is not None:
|
| 119 |
+
image.close()
|
| 120 |
+
if bitmap is not None:
|
| 121 |
+
bitmap.close()
|
| 122 |
+
if page is not None:
|
| 123 |
+
page.close()
|
| 124 |
+
|
| 125 |
+
def close(self) -> None:
|
| 126 |
+
if self._document is not None:
|
| 127 |
+
self._document.close()
|
| 128 |
+
self._document = None
|
src/unlimited_ocr_rdna4/postprocess.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
|
| 5 |
+
EOS_STOP = "<|end▁of▁sentence|>"
|
| 6 |
+
_NONTEXT_LINE_RE = re.compile(r"(?m)^[ \t]*(?:\[\s*Non(?:[ -]?Text)\s*\]|NonText)[ \t]*(?:\n|$)")
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def _replace_complete_tags(text: str, opening: str, closing: str, *, preserve_content: bool) -> str:
|
| 10 |
+
"""Replace only complete exact tag pairs; malformed input is preserved."""
|
| 11 |
+
chunks: list[str] = []
|
| 12 |
+
cursor = 0
|
| 13 |
+
while True:
|
| 14 |
+
start = text.find(opening, cursor)
|
| 15 |
+
if start < 0:
|
| 16 |
+
chunks.append(text[cursor:])
|
| 17 |
+
break
|
| 18 |
+
end = text.find(closing, start + len(opening))
|
| 19 |
+
if end < 0:
|
| 20 |
+
chunks.append(text[cursor:])
|
| 21 |
+
break
|
| 22 |
+
chunks.append(text[cursor:start])
|
| 23 |
+
if preserve_content:
|
| 24 |
+
chunks.append(text[start + len(opening) : end])
|
| 25 |
+
cursor = end + len(closing)
|
| 26 |
+
return "".join(chunks)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def clean_model_output(text: str) -> str:
|
| 30 |
+
"""Remove exact model sentinels while preserving recognized content verbatim."""
|
| 31 |
+
if text.endswith(EOS_STOP):
|
| 32 |
+
text = text[: -len(EOS_STOP)]
|
| 33 |
+
text = _replace_complete_tags(text, "<|ref|>", "<|/ref|>", preserve_content=True)
|
| 34 |
+
text = _replace_complete_tags(text, "<|det|>", "<|/det|>", preserve_content=False)
|
| 35 |
+
text = _NONTEXT_LINE_RE.sub("", text)
|
| 36 |
+
return text.strip()
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def repetition_warning(text: str) -> str | None:
|
| 40 |
+
"""Return a warning for obvious terminal repetition loops."""
|
| 41 |
+
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
| 42 |
+
for index in range(2, len(lines)):
|
| 43 |
+
if lines[index] == lines[index - 1] == lines[index - 2] and len(lines[index]) >= 8:
|
| 44 |
+
return f"repeated line detected three times: {lines[index][:80]!r}"
|
| 45 |
+
|
| 46 |
+
compact = re.sub(r"\s+", " ", text).strip()
|
| 47 |
+
for width in (64, 48, 32):
|
| 48 |
+
if len(compact) >= width * 3:
|
| 49 |
+
tail = compact[-width:]
|
| 50 |
+
if compact[-width * 3 :] == tail * 3:
|
| 51 |
+
return f"repeated {width}-character suffix detected"
|
| 52 |
+
return None
|
src/unlimited_ocr_rdna4/runtime.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
from dataclasses import asdict, dataclass
|
| 6 |
+
from importlib.metadata import PackageNotFoundError, version
|
| 7 |
+
from typing import Any
|
| 8 |
+
|
| 9 |
+
from .constants import (
|
| 10 |
+
HARDWARE_VERIFIED_ARCHITECTURES,
|
| 11 |
+
SUPPORTED_ARCHITECTURES,
|
| 12 |
+
VERIFIED_HIP_VERSION,
|
| 13 |
+
VERIFIED_PACKAGE_VERSIONS,
|
| 14 |
+
)
|
| 15 |
+
from .errors import RuntimeEnvironmentError
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@dataclass(frozen=True)
|
| 19 |
+
class DeviceInfo:
|
| 20 |
+
index: int
|
| 21 |
+
name: str
|
| 22 |
+
architecture: str | None
|
| 23 |
+
uuid: str | None
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@dataclass(frozen=True)
|
| 27 |
+
class RuntimeInfo:
|
| 28 |
+
torch_version: str
|
| 29 |
+
hip_version: str | None
|
| 30 |
+
cuda_available: bool
|
| 31 |
+
bf16_supported: bool
|
| 32 |
+
visible_devices: int
|
| 33 |
+
devices: tuple[DeviceInfo, ...]
|
| 34 |
+
package_versions: dict[str, str | None]
|
| 35 |
+
accepted_architecture: bool
|
| 36 |
+
hardware_verified: bool
|
| 37 |
+
software_verified: bool
|
| 38 |
+
warnings: tuple[str, ...]
|
| 39 |
+
|
| 40 |
+
def to_dict(self) -> dict[str, Any]:
|
| 41 |
+
return asdict(self)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def select_device(device: str | None) -> None:
|
| 45 |
+
if device is None:
|
| 46 |
+
return
|
| 47 |
+
if "torch" in sys.modules:
|
| 48 |
+
raise RuntimeEnvironmentError("GPU selection must happen before importing PyTorch")
|
| 49 |
+
existing_rocr = os.environ.get("ROCR_VISIBLE_DEVICES")
|
| 50 |
+
if existing_rocr is not None and existing_rocr != device:
|
| 51 |
+
raise RuntimeEnvironmentError(
|
| 52 |
+
f"--device {device!r} conflicts with existing ROCR_VISIBLE_DEVICES={existing_rocr!r}; unset one explicitly"
|
| 53 |
+
)
|
| 54 |
+
conflicts = [name for name in ("HIP_VISIBLE_DEVICES", "CUDA_VISIBLE_DEVICES") if name in os.environ]
|
| 55 |
+
if conflicts:
|
| 56 |
+
raise RuntimeEnvironmentError(
|
| 57 |
+
f"--device cannot be combined with existing {', '.join(conflicts)}; "
|
| 58 |
+
"unset the conflicting visibility variable"
|
| 59 |
+
)
|
| 60 |
+
os.environ["ROCR_VISIBLE_DEVICES"] = device
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _installed_versions() -> dict[str, str | None]:
|
| 64 |
+
installed: dict[str, str | None] = {}
|
| 65 |
+
for package in VERIFIED_PACKAGE_VERSIONS:
|
| 66 |
+
try:
|
| 67 |
+
installed[package] = version(package)
|
| 68 |
+
except PackageNotFoundError:
|
| 69 |
+
installed[package] = None
|
| 70 |
+
return installed
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def runtime_issues(info: RuntimeInfo, *, require_single: bool) -> tuple[str, ...]:
|
| 74 |
+
issues: list[str] = []
|
| 75 |
+
if info.hip_version is None or not info.cuda_available:
|
| 76 |
+
issues.append("PyTorch does not see a ROCm GPU; a CUDA-only wheel is not sufficient")
|
| 77 |
+
if require_single and info.visible_devices != 1:
|
| 78 |
+
issues.append(f"expected exactly one visible GPU, found {info.visible_devices}; pass --device INDEX_OR_UUID")
|
| 79 |
+
if not info.accepted_architecture:
|
| 80 |
+
architectures = ", ".join(device.architecture or "unknown" for device in info.devices) or "none"
|
| 81 |
+
issues.append(f"expected RDNA 4 (gfx1200/gfx1201), found: {architectures}")
|
| 82 |
+
if info.cuda_available and not info.bf16_supported:
|
| 83 |
+
issues.append("the selected GPU/runtime does not report BF16 support")
|
| 84 |
+
return tuple(issues)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def inspect_runtime(*, device: str | None = None, require_single: bool = False, validate: bool = True) -> RuntimeInfo:
|
| 88 |
+
select_device(device)
|
| 89 |
+
try:
|
| 90 |
+
import torch
|
| 91 |
+
except ImportError as exc:
|
| 92 |
+
raise RuntimeEnvironmentError(
|
| 93 |
+
"ROCm PyTorch is not installed. Run scripts/bootstrap-rocm.sh from the repository checkout."
|
| 94 |
+
) from exc
|
| 95 |
+
|
| 96 |
+
hip_version = getattr(torch.version, "hip", None)
|
| 97 |
+
cuda_available = bool(torch.cuda.is_available())
|
| 98 |
+
count = torch.cuda.device_count() if cuda_available else 0
|
| 99 |
+
devices: list[DeviceInfo] = []
|
| 100 |
+
for index in range(count):
|
| 101 |
+
properties = torch.cuda.get_device_properties(index)
|
| 102 |
+
devices.append(
|
| 103 |
+
DeviceInfo(
|
| 104 |
+
index=index,
|
| 105 |
+
name=properties.name,
|
| 106 |
+
architecture=getattr(properties, "gcnArchName", None),
|
| 107 |
+
uuid=str(getattr(properties, "uuid", "")) or None,
|
| 108 |
+
)
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
accepted_architecture = bool(devices) and all(device.architecture in SUPPORTED_ARCHITECTURES for device in devices)
|
| 112 |
+
hardware_verified = bool(devices) and all(
|
| 113 |
+
device.architecture in HARDWARE_VERIFIED_ARCHITECTURES for device in devices
|
| 114 |
+
)
|
| 115 |
+
package_versions = _installed_versions()
|
| 116 |
+
software_verified = hip_version == VERIFIED_HIP_VERSION and all(
|
| 117 |
+
package_versions[name] == expected for name, expected in VERIFIED_PACKAGE_VERSIONS.items()
|
| 118 |
+
)
|
| 119 |
+
warnings: list[str] = []
|
| 120 |
+
if accepted_architecture and not hardware_verified:
|
| 121 |
+
warnings.append(
|
| 122 |
+
"gfx1200 is accepted by the RDNA 4 guard but has not completed this project's GPU acceptance gate"
|
| 123 |
+
)
|
| 124 |
+
if not software_verified:
|
| 125 |
+
warnings.append("the installed software stack differs from the exact validated versions")
|
| 126 |
+
info = RuntimeInfo(
|
| 127 |
+
torch_version=torch.__version__,
|
| 128 |
+
hip_version=hip_version,
|
| 129 |
+
cuda_available=cuda_available,
|
| 130 |
+
bf16_supported=bool(torch.cuda.is_bf16_supported()) if cuda_available else False,
|
| 131 |
+
visible_devices=count,
|
| 132 |
+
devices=tuple(devices),
|
| 133 |
+
package_versions=package_versions,
|
| 134 |
+
accepted_architecture=accepted_architecture,
|
| 135 |
+
hardware_verified=hardware_verified,
|
| 136 |
+
software_verified=software_verified,
|
| 137 |
+
warnings=tuple(warnings),
|
| 138 |
+
)
|
| 139 |
+
issues = runtime_issues(info, require_single=require_single)
|
| 140 |
+
if validate and issues:
|
| 141 |
+
raise RuntimeEnvironmentError("; ".join(issues))
|
| 142 |
+
return info
|
tests/fixtures/rdna4-smoke.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cdd2b484d0ac90bd98b489dd97565a65eb17246f713359524cddd41d78cc10cb
|
| 3 |
+
size 152141
|
tests/fixtures/rdna4-smoke.png
ADDED
|
Git LFS Details
|
tests/test_bootstrap.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def test_bootstrap_requires_exact_rocm_version() -> None:
|
| 7 |
+
script = (ROOT / "scripts/bootstrap-rocm.sh").read_text(encoding="utf-8")
|
| 8 |
+
assert '[[ $HOST_ROCM != "$ROCM_RELEASE" ]]' in script
|
| 9 |
+
assert "[[ $HOST_ROCM != 7.2.1* ]]" not in script
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def test_bootstrap_uses_hash_lock_and_local_wheel() -> None:
|
| 13 |
+
script = (ROOT / "scripts/bootstrap-rocm.sh").read_text(encoding="utf-8")
|
| 14 |
+
assert "--require-hashes --only-binary=:all:" in script
|
| 15 |
+
assert "pip wheel --no-build-isolation --no-deps" in script
|
| 16 |
+
assert "pip install --force-reinstall --no-deps" in script
|
| 17 |
+
assert "pip install --upgrade" not in script
|
tests/test_cli.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
from unlimited_ocr_rdna4.cli import run
|
| 5 |
+
from unlimited_ocr_rdna4.infer import default_output_path
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def test_prepare_dry_run_json_after_subcommand(capsys, tmp_path) -> None:
|
| 9 |
+
code = run(
|
| 10 |
+
[
|
| 11 |
+
"prepare",
|
| 12 |
+
"--model-dir",
|
| 13 |
+
str(tmp_path / "model"),
|
| 14 |
+
"--cache-dir",
|
| 15 |
+
str(tmp_path / "cache"),
|
| 16 |
+
"--dry-run",
|
| 17 |
+
"--json",
|
| 18 |
+
]
|
| 19 |
+
)
|
| 20 |
+
assert code == 0
|
| 21 |
+
payload = json.loads(capsys.readouterr().out)
|
| 22 |
+
assert payload["schema_version"] == 1
|
| 23 |
+
assert payload["status"] == "dry-run"
|
| 24 |
+
assert payload["prepared"] is False
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def test_no_command_returns_usage_error(capsys) -> None:
|
| 28 |
+
assert run([]) == 2
|
| 29 |
+
assert "usage:" in capsys.readouterr().err
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def test_default_output_path() -> None:
|
| 33 |
+
assert default_output_path(Path("invoice.pdf")) == Path("invoice.pdf.ocr.md")
|
tests/test_infer_output.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
from unlimited_ocr_rdna4.errors import InferenceError
|
| 6 |
+
from unlimited_ocr_rdna4.infer import _publish_text, _validated_output_path
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def test_output_must_not_alias_input(tmp_path) -> None:
|
| 10 |
+
source = tmp_path / "page.png"
|
| 11 |
+
source.write_bytes(b"source")
|
| 12 |
+
alias = tmp_path / "alias.md"
|
| 13 |
+
os.link(source, alias)
|
| 14 |
+
with pytest.raises(InferenceError, match="same file"):
|
| 15 |
+
_validated_output_path(source.resolve(), alias, force=True)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_output_symlink_is_rejected_even_with_force(tmp_path) -> None:
|
| 19 |
+
source = tmp_path / "page.png"
|
| 20 |
+
source.write_bytes(b"source")
|
| 21 |
+
output = tmp_path / "output.md"
|
| 22 |
+
output.symlink_to(source)
|
| 23 |
+
with pytest.raises(InferenceError, match="symlink"):
|
| 24 |
+
_validated_output_path(source.resolve(), output, force=True)
|
| 25 |
+
assert source.read_bytes() == b"source"
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def test_non_force_publish_never_overwrites(tmp_path) -> None:
|
| 29 |
+
output = tmp_path / "output.md"
|
| 30 |
+
output.write_text("existing\n", encoding="utf-8")
|
| 31 |
+
with pytest.raises(InferenceError, match="created while inference"):
|
| 32 |
+
_publish_text(output, "new", force=False)
|
| 33 |
+
assert output.read_text(encoding="utf-8") == "existing\n"
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def test_force_publish_replaces_regular_file(tmp_path) -> None:
|
| 37 |
+
output = tmp_path / "output.md"
|
| 38 |
+
output.write_text("old\n", encoding="utf-8")
|
| 39 |
+
_publish_text(output, "new", force=True)
|
| 40 |
+
assert output.read_text(encoding="utf-8") == "new\n"
|
tests/test_model_store.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import hashlib
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import unlimited_ocr_rdna4.model_store as model_store
|
| 6 |
+
from unlimited_ocr_rdna4.errors import ModelIntegrityError
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def _synthetic_upstream() -> str:
|
| 10 |
+
lines = [
|
| 11 |
+
"from .modeling_deepseekv2 import DeepseekV2Model, DeepseekV2ForCausalLM",
|
| 12 |
+
"cor_list = eval(ref_text[2])",
|
| 13 |
+
"lines = eval(outputs)['Line']['line']",
|
| 14 |
+
"line_type = eval(outputs)['Line']['line_type']",
|
| 15 |
+
"endpoints = eval(outputs)['Line']['line_endpoint']",
|
| 16 |
+
"p0 = eval(line.split(' -- ')[0])",
|
| 17 |
+
"p1 = eval(line.split(' -- ')[-1])",
|
| 18 |
+
"(x, y) = eval(endpoint.split(': ')[1])",
|
| 19 |
+
"images_seq_mask[idx].unsqueeze(-1).cuda()",
|
| 20 |
+
]
|
| 21 |
+
for _ in range(3):
|
| 22 |
+
lines.extend(
|
| 23 |
+
[
|
| 24 |
+
" input_ids=input_ids.unsqueeze(0).cuda(),",
|
| 25 |
+
" eos_token_id=tokenizer.eos_token_id,",
|
| 26 |
+
]
|
| 27 |
+
)
|
| 28 |
+
return "\n".join(lines) + "\n"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def test_patch_is_exact_and_idempotent(monkeypatch) -> None:
|
| 32 |
+
source = _synthetic_upstream()
|
| 33 |
+
monkeypatch.setattr(model_store, "UPSTREAM_MODEL_CODE_SHA256", model_store.sha256_text(source))
|
| 34 |
+
monkeypatch.setattr(model_store, "PATCHED_MODEL_CODE_SHA256", "__TO_BE_FILLED__")
|
| 35 |
+
patched = model_store.patch_model_source_text(source)
|
| 36 |
+
patched_hash = model_store.sha256_text(patched)
|
| 37 |
+
assert patched.count("ast.literal_eval(") == 7
|
| 38 |
+
assert patched.count("attention_mask=torch.ones_like") == 3
|
| 39 |
+
assert patched.count("pad_token_id=tokenizer.eos_token_id") == 3
|
| 40 |
+
assert ".to(inputs_embeds.device)" in patched
|
| 41 |
+
|
| 42 |
+
monkeypatch.setattr(model_store, "PATCHED_MODEL_CODE_SHA256", patched_hash)
|
| 43 |
+
assert model_store.patch_model_source_text(patched) == patched
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def test_patch_rejects_unknown_source() -> None:
|
| 47 |
+
with pytest.raises(ModelIntegrityError, match="refusing to patch unknown model code"):
|
| 48 |
+
model_store.patch_model_source_text("unknown")
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def test_verify_model_reports_missing_directory(tmp_path) -> None:
|
| 52 |
+
with pytest.raises(ModelIntegrityError, match="real directory"):
|
| 53 |
+
model_store.verify_model(tmp_path / "missing")
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def test_verify_model_with_small_fixture(tmp_path, monkeypatch) -> None:
|
| 57 |
+
code = "patched model code\n"
|
| 58 |
+
weight = b"weights"
|
| 59 |
+
(tmp_path / "modeling_unlimitedocr.py").write_text(code, encoding="utf-8")
|
| 60 |
+
(tmp_path / "weight.bin").write_bytes(weight)
|
| 61 |
+
monkeypatch.setattr(model_store, "MODEL_WEIGHT_FILE", "weight.bin")
|
| 62 |
+
monkeypatch.setattr(model_store, "MODEL_WEIGHT_BYTES", len(weight))
|
| 63 |
+
monkeypatch.setattr(model_store, "MODEL_WEIGHT_SHA256", hashlib.sha256(weight).hexdigest())
|
| 64 |
+
monkeypatch.setattr(model_store, "PATCHED_MODEL_CODE_SHA256", hashlib.sha256(code.encode()).hexdigest())
|
| 65 |
+
monkeypatch.setattr(
|
| 66 |
+
model_store,
|
| 67 |
+
"MODEL_PAYLOAD_SHA256",
|
| 68 |
+
{
|
| 69 |
+
"modeling_unlimitedocr.py": hashlib.sha256(code.encode()).hexdigest(),
|
| 70 |
+
"weight.bin": hashlib.sha256(weight).hexdigest(),
|
| 71 |
+
},
|
| 72 |
+
)
|
| 73 |
+
model_store._write_manifest(tmp_path)
|
| 74 |
+
status = model_store.verify_model(tmp_path)
|
| 75 |
+
assert status.prepared
|
| 76 |
+
assert status.weight_sha256 == hashlib.sha256(weight).hexdigest()
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def test_verify_model_rejects_unexpected_file(tmp_path, monkeypatch) -> None:
|
| 80 |
+
code = b"code"
|
| 81 |
+
weight = b"weight"
|
| 82 |
+
(tmp_path / "code.py").write_bytes(code)
|
| 83 |
+
(tmp_path / "weight.bin").write_bytes(weight)
|
| 84 |
+
monkeypatch.setattr(model_store, "MODEL_WEIGHT_FILE", "weight.bin")
|
| 85 |
+
monkeypatch.setattr(model_store, "MODEL_WEIGHT_BYTES", len(weight))
|
| 86 |
+
monkeypatch.setattr(
|
| 87 |
+
model_store,
|
| 88 |
+
"MODEL_PAYLOAD_SHA256",
|
| 89 |
+
{"code.py": hashlib.sha256(code).hexdigest(), "weight.bin": hashlib.sha256(weight).hexdigest()},
|
| 90 |
+
)
|
| 91 |
+
model_store._write_manifest(tmp_path)
|
| 92 |
+
(tmp_path / "configuration_surprise.py").write_text("raise SystemExit", encoding="utf-8")
|
| 93 |
+
with pytest.raises(ModelIntegrityError, match="unexpected"):
|
| 94 |
+
model_store.verify_model(tmp_path)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def test_verify_model_rejects_symlinked_payload(tmp_path, monkeypatch) -> None:
|
| 98 |
+
target = tmp_path / "target"
|
| 99 |
+
target.write_bytes(b"weight")
|
| 100 |
+
(tmp_path / "weight.bin").symlink_to(target)
|
| 101 |
+
monkeypatch.setattr(model_store, "MODEL_WEIGHT_FILE", "weight.bin")
|
| 102 |
+
monkeypatch.setattr(model_store, "MODEL_WEIGHT_BYTES", len(b"weight"))
|
| 103 |
+
monkeypatch.setattr(
|
| 104 |
+
model_store,
|
| 105 |
+
"MODEL_PAYLOAD_SHA256",
|
| 106 |
+
{"weight.bin": hashlib.sha256(b"weight").hexdigest(), "target": hashlib.sha256(b"weight").hexdigest()},
|
| 107 |
+
)
|
| 108 |
+
model_store._write_manifest(tmp_path)
|
| 109 |
+
with pytest.raises(ModelIntegrityError, match="missing regular files"):
|
| 110 |
+
model_store.verify_model(tmp_path)
|
tests/test_paths.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
from unlimited_ocr_rdna4.constants import MODEL_REVISION
|
| 4 |
+
from unlimited_ocr_rdna4.paths import default_cache_dir, default_model_dir
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def test_xdg_defaults(monkeypatch) -> None:
|
| 8 |
+
monkeypatch.delenv("UNLIMITED_OCR_MODEL_DIR", raising=False)
|
| 9 |
+
monkeypatch.setenv("XDG_DATA_HOME", "/data-home")
|
| 10 |
+
monkeypatch.setenv("XDG_CACHE_HOME", "/cache-home")
|
| 11 |
+
assert default_model_dir() == Path("/data-home/unlimited-ocr-rdna4/models") / MODEL_REVISION
|
| 12 |
+
assert default_cache_dir() == Path("/cache-home/unlimited-ocr-rdna4")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def test_model_dir_override(monkeypatch) -> None:
|
| 16 |
+
monkeypatch.setenv("UNLIMITED_OCR_MODEL_DIR", "/models/pinned")
|
| 17 |
+
assert default_model_dir() == Path("/models/pinned")
|
tests/test_pdf.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
from PIL import Image
|
| 3 |
+
|
| 4 |
+
from unlimited_ocr_rdna4.errors import InferenceError
|
| 5 |
+
from unlimited_ocr_rdna4.pdf import PDFPageRenderer
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def _make_pdf(path) -> None:
|
| 9 |
+
image = Image.new("RGB", (120, 80), "white")
|
| 10 |
+
try:
|
| 11 |
+
image.save(path, format="PDF", resolution=72)
|
| 12 |
+
finally:
|
| 13 |
+
image.close()
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def test_pdf_renders_one_page_at_a_time(tmp_path) -> None:
|
| 17 |
+
pdf = tmp_path / "fixture.pdf"
|
| 18 |
+
_make_pdf(pdf)
|
| 19 |
+
with PDFPageRenderer(
|
| 20 |
+
pdf,
|
| 21 |
+
tmp_path,
|
| 22 |
+
dpi=72,
|
| 23 |
+
start_page=1,
|
| 24 |
+
max_pages=1,
|
| 25 |
+
max_page_pixels=20_000,
|
| 26 |
+
max_total_pixels=20_000,
|
| 27 |
+
max_page_rendered_bytes=1_000_000,
|
| 28 |
+
max_rendered_bytes=1_000_000,
|
| 29 |
+
) as renderer:
|
| 30 |
+
assert tuple(renderer.page_numbers) == (1,)
|
| 31 |
+
rendered = renderer.render(1)
|
| 32 |
+
assert rendered.is_file()
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def test_pdf_rejects_page_over_pixel_limit(tmp_path) -> None:
|
| 36 |
+
pdf = tmp_path / "fixture.pdf"
|
| 37 |
+
_make_pdf(pdf)
|
| 38 |
+
with (
|
| 39 |
+
PDFPageRenderer(
|
| 40 |
+
pdf,
|
| 41 |
+
tmp_path,
|
| 42 |
+
dpi=72,
|
| 43 |
+
start_page=1,
|
| 44 |
+
max_pages=1,
|
| 45 |
+
max_page_pixels=1_000,
|
| 46 |
+
max_total_pixels=20_000,
|
| 47 |
+
max_page_rendered_bytes=1_000_000,
|
| 48 |
+
max_rendered_bytes=1_000_000,
|
| 49 |
+
) as renderer,
|
| 50 |
+
pytest.raises(InferenceError, match="would render"),
|
| 51 |
+
):
|
| 52 |
+
renderer.render(1)
|
tests/test_postprocess.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from unlimited_ocr_rdna4.postprocess import EOS_STOP, clean_model_output, repetition_warning
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def test_clean_model_output_preserves_unicode_and_content() -> None:
|
| 5 |
+
raw = (
|
| 6 |
+
"<|det|>header [1, 2, 3, 4]<|/det|>Überblick\n"
|
| 7 |
+
"<|det|>table [1, 2, 3, 4]<|/det|><table><tr><td>€48.50</td></tr></table>\n"
|
| 8 |
+
"[Non-Text]\n"
|
| 9 |
+
"\\coloneqq"
|
| 10 |
+
f"{EOS_STOP}"
|
| 11 |
+
)
|
| 12 |
+
assert clean_model_output(raw) == "Überblick\n<table><tr><td>€48.50</td></tr></table>\n\\coloneqq"
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def test_clean_model_output_removes_combined_reference_tag() -> None:
|
| 16 |
+
raw = "<|ref|>title<|/ref|><|det|>[[1, 2, 3, 4]]<|/det|>Actual title"
|
| 17 |
+
assert clean_model_output(raw) == "titleActual title"
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def test_clean_model_output_preserves_ordinary_words_and_tex() -> None:
|
| 21 |
+
raw = "NonTextile material\nA \\eqqcolon B\nembedded NonText label"
|
| 22 |
+
assert clean_model_output(raw) == raw
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def test_clean_model_output_preserves_malformed_unclosed_tags() -> None:
|
| 26 |
+
raw = "before <|det|>coordinate and recognized content"
|
| 27 |
+
assert clean_model_output(raw) == raw
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def test_repetition_warning_detects_three_lines() -> None:
|
| 31 |
+
text = "start\nrepeating output\nrepeating output\nrepeating output"
|
| 32 |
+
assert "repeated line" in (repetition_warning(text) or "")
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def test_repetition_warning_accepts_normal_text() -> None:
|
| 36 |
+
assert repetition_warning("first\nsecond\nthird") is None
|
tests/test_runtime.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
import pytest
|
| 5 |
+
|
| 6 |
+
from unlimited_ocr_rdna4.errors import RuntimeEnvironmentError
|
| 7 |
+
from unlimited_ocr_rdna4.runtime import DeviceInfo, RuntimeInfo, select_device
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def test_runtime_info_serializes() -> None:
|
| 11 |
+
info = RuntimeInfo(
|
| 12 |
+
torch_version="2.9.1+rocm7.2.1",
|
| 13 |
+
hip_version="7.2",
|
| 14 |
+
cuda_available=True,
|
| 15 |
+
bf16_supported=True,
|
| 16 |
+
visible_devices=1,
|
| 17 |
+
devices=(DeviceInfo(0, "AMD Radeon RX 9070 XT", "gfx1201", "uuid"),),
|
| 18 |
+
package_versions={"torch": "different"},
|
| 19 |
+
accepted_architecture=True,
|
| 20 |
+
hardware_verified=True,
|
| 21 |
+
software_verified=False,
|
| 22 |
+
warnings=("different stack",),
|
| 23 |
+
)
|
| 24 |
+
payload = info.to_dict()
|
| 25 |
+
assert payload["devices"][0]["architecture"] == "gfx1201"
|
| 26 |
+
assert payload["hardware_verified"] is True
|
| 27 |
+
assert payload["software_verified"] is False
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def test_device_selection_rejects_conflicting_visibility(monkeypatch) -> None:
|
| 31 |
+
monkeypatch.delitem(sys.modules, "torch", raising=False)
|
| 32 |
+
monkeypatch.setenv("HIP_VISIBLE_DEVICES", "2")
|
| 33 |
+
with pytest.raises(RuntimeEnvironmentError, match="HIP_VISIBLE_DEVICES"):
|
| 34 |
+
select_device("GPU-example")
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def test_device_selection_preserves_matching_rocr_value(monkeypatch) -> None:
|
| 38 |
+
monkeypatch.delitem(sys.modules, "torch", raising=False)
|
| 39 |
+
monkeypatch.delenv("HIP_VISIBLE_DEVICES", raising=False)
|
| 40 |
+
monkeypatch.delenv("CUDA_VISIBLE_DEVICES", raising=False)
|
| 41 |
+
monkeypatch.setenv("ROCR_VISIBLE_DEVICES", "GPU-example")
|
| 42 |
+
select_device("GPU-example")
|
| 43 |
+
assert os.environ["ROCR_VISIBLE_DEVICES"] == "GPU-example"
|