Image-Text-to-Text
MLX
Safetensors
unlimited-ocr
ax-engine
mlx-vlm
ocr
mxfp8
int8
apple-silicon
automatosx
conversational
8-bit precision
Instructions to use AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8 with MLX:
# Make sure mlx-vlm is installed # pip install --upgrade mlx-vlm from mlx_vlm import load, generate from mlx_vlm.prompt_utils import apply_chat_template from mlx_vlm.utils import load_config # Load the model model, processor = load("AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8") config = load_config("AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8") # Prepare input image = ["http://images.cocodataset.org/val2017/000000039769.jpg"] prompt = "Describe this image." # Apply chat template formatted_prompt = apply_chat_template( processor, config, prompt, num_images=1 ) # Generate output output = generate(model, processor, formatted_prompt, image) print(output) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
Publish verified OCR-aware MXFP8 checkpoint
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- LICENSE +21 -0
- README.md +84 -0
- benchmarks/datasets.md +57 -0
- benchmarks/evaluate_cer.py +279 -0
- benchmarks/evaluate_tables.py +285 -0
- benchmarks/normalize_output.py +128 -0
- benchmarks/rswa_validation.py +500 -0
- benchmarks/run_accuracy.py +249 -0
- benchmarks/run_performance.py +280 -0
- chat_template.jinja +1 -0
- config.json +1321 -0
- docs/PRD.md +153 -0
- docs/TECHNICAL_SPEC.md +260 -0
- docs/adr/0001-verified-ocr-aware-quantization.md +90 -0
- docs/adr/0002-calibrated-affine8-lm-head.md +60 -0
- examples/batch_directory.py +52 -0
- examples/multi_page_pdf.py +53 -0
- examples/single_image.py +36 -0
- generation_config.json +17 -0
- model.safetensors +3 -0
- model.safetensors.index.json +759 -0
- precision_map.json +110 -0
- preprocessing_config.json +81 -0
- processor_config.json +28 -0
- pyproject.toml +53 -0
- quantization/calibrate_precision.py +142 -0
- quantization/layer_sensitivity.py +402 -0
- quantization/mixed_precision_convert.py +323 -0
- quantization/precision_map.json +31 -0
- quantization/release_gate.py +584 -0
- quantization/run_pipeline.py +340 -0
- quantization_config.json +26 -0
- quantization_summary.json +257 -0
- release/bf16_accuracy.json +195 -0
- release/calibration_results.json +92 -0
- release/candidate_accuracy.json +195 -0
- release/candidate_performance.json +68 -0
- release/candidate_rswa.json +75 -0
- release/generated_precision_map.json +110 -0
- release/provenance.json +174 -0
- release/reference_accuracy.json +195 -0
- release/reference_performance.json +68 -0
- release/release_manifest.json +1191 -0
- release/sensitivity_results.json +516 -0
- special_tokens_map.json +39 -0
- src/unlimited_ocr/__init__.py +17 -0
- src/unlimited_ocr/adapter_registry.json +17 -0
- src/unlimited_ocr/cli.py +368 -0
- src/unlimited_ocr/engine.py +566 -0
- src/unlimited_ocr/output.py +492 -0
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 AutomatosX
|
| 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,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
base_model: baidu/Unlimited-OCR
|
| 4 |
+
library_name: mlx-vlm
|
| 5 |
+
pipeline_tag: image-text-to-text
|
| 6 |
+
tags:
|
| 7 |
+
- mlx
|
| 8 |
+
- mlx-vlm
|
| 9 |
+
- ocr
|
| 10 |
+
- mxfp8
|
| 11 |
+
- int8
|
| 12 |
+
- apple-silicon
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
# AX Unlimited-OCR 3B MoE — MLX MXFP8
|
| 16 |
+
|
| 17 |
+
This is an AutomatosX sensitivity-guided MLX conversion of
|
| 18 |
+
[`baidu/Unlimited-OCR`](https://huggingface.co/baidu/Unlimited-OCR). It was
|
| 19 |
+
created from the upstream BF16 weights. The
|
| 20 |
+
[`sahilchachra/unlimited-ocr-mxfp8-mlx`](https://huggingface.co/sahilchachra/unlimited-ocr-mxfp8-mlx)
|
| 21 |
+
checkpoint was used only as a comparison reference; these are not repackaged
|
| 22 |
+
Sahil weights.
|
| 23 |
+
|
| 24 |
+
## What was optimized
|
| 25 |
+
|
| 26 |
+
Each supported module group was quantized independently and evaluated for OCR
|
| 27 |
+
CER, digit CER, and table structure. Eligible language linear layers use MXFP8
|
| 28 |
+
(8 data bits, group size 32). A joint calibration then selected affine INT8
|
| 29 |
+
(group size 32) for the LM head because BF16 missed the throughput floor and
|
| 30 |
+
MXFP8 missed the CER ceiling. OCR-critical or sensitive groups remain BF16.
|
| 31 |
+
Retained groups: vision_encoder.
|
| 32 |
+
|
| 33 |
+
The model uses mlx-vlm's native `model_type=unlimited-ocr` implementation and a
|
| 34 |
+
sliding-window size of 128.
|
| 35 |
+
|
| 36 |
+
## AutomatosX validation
|
| 37 |
+
|
| 38 |
+
These are local release measurements, not upstream paper benchmark claims.
|
| 39 |
+
The content-addressed evaluation set had 12 samples
|
| 40 |
+
(12 with digits, 3 with
|
| 41 |
+
CJK, and 3 with tables), digest
|
| 42 |
+
`78ca3e7c9c7584aea30b04176716a6dadf49ccf0f2bead959cbaeb9aeac68f30`.
|
| 43 |
+
|
| 44 |
+
| Checkpoint | Mean CER | Digit CER | Table score | Decode tok/s |
|
| 45 |
+
|---|---:|---:|---:|---:|
|
| 46 |
+
| Upstream BF16 | 0.239188 | 0.087340 | 1.000000 | — |
|
| 47 |
+
| Sahil MXFP8 reference | 1.057249 | 0.209153 | 1.000000 | 312.73 |
|
| 48 |
+
| AutomatosX candidate | 0.237384 | 0.085733 | 1.000000 | 297.20 |
|
| 49 |
+
|
| 50 |
+
Weight size: 3.579 GiB
|
| 51 |
+
Aggregate weight SHA-256: `a4f6e9934f90564906f42929542ad68493f0c6187ad431128e447debbf52b6f4`
|
| 52 |
+
|
| 53 |
+
## Release gates
|
| 54 |
+
|
| 55 |
+
- ✅ `weights_are_distinct` — actual `a4f6e9934f90564906f42929542ad68493f0c6187ad431128e447debbf52b6f4`, limit `different from 439c6acfe5e277537dfe3368b94a145a6b0da4c39e4bd00582977d030f292ad1`
|
| 56 |
+
- ✅ `weight_size_gb` — actual `3.5792221864685416`, limit `4.5`
|
| 57 |
+
- ✅ `native_model_metadata` — actual `{'architecture': True, 'model_type': True, 'mxfp8': True, 'sliding_window': True, 'processor_class': True, 'sft_format': True}`, limit `True`
|
| 58 |
+
- ✅ `evaluation_coverage` — actual `{'samples': 12, 'digit': 12, 'cjk': 3, 'table': 3}`, limit `all counts > 0`
|
| 59 |
+
- ✅ `same_evaluation_samples` — actual `[12, 12, 12]`, limit `12`
|
| 60 |
+
- ✅ `candidate_cer_vs_bf16` — actual `-0.0018033315701714847`, limit `0.01`
|
| 61 |
+
- ✅ `candidate_cer_vs_reference` — actual `-0.8198643918447561`, limit `0.005`
|
| 62 |
+
- ✅ `candidate_digit_cer_vs_bf16` — actual `-0.0016074315849917697`, limit `0.01`
|
| 63 |
+
- ✅ `candidate_table_score_vs_bf16` — actual `0.0`, limit `0.01`
|
| 64 |
+
- ✅ `candidate_tps_vs_reference` — actual `0.9503587809120344`, limit `0.9`
|
| 65 |
+
- ✅ `rswa_8k_bounded` — actual `{'cache_bounded': True, 'tps_stable': True, '8k_test_passed': True}`, limit `True`
|
| 66 |
+
|
| 67 |
+
Full per-sample evidence, sensitivity results, precision rules, PRD, ADR, and
|
| 68 |
+
technical specification are included under `release/`, `quantization/`, and
|
| 69 |
+
`docs/`.
|
| 70 |
+
|
| 71 |
+
## Usage
|
| 72 |
+
|
| 73 |
+
```bash
|
| 74 |
+
pip install mlx-vlm
|
| 75 |
+
python -m mlx_vlm.generate \
|
| 76 |
+
--model AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8 \
|
| 77 |
+
--image document.png \
|
| 78 |
+
--prompt '<image>document parsing.' \
|
| 79 |
+
--max-tokens 4096
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
Apple Silicon and a recent `mlx-vlm` release with native Unlimited-OCR support
|
| 83 |
+
are required. This is quantization/conversion work, not foundation-model
|
| 84 |
+
retraining or domain fine-tuning.
|
benchmarks/datasets.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Benchmark Datasets
|
| 2 |
+
|
| 3 |
+
## Evaluation Dataset Requirements
|
| 4 |
+
|
| 5 |
+
The evaluation set must be separate from any calibration data used for quantization decisions.
|
| 6 |
+
|
| 7 |
+
### Recommended Composition (minimum 500 samples)
|
| 8 |
+
|
| 9 |
+
| Category | Minimum Samples |
|
| 10 |
+
|---------------------------|----------------:|
|
| 11 |
+
| English printed documents | 75 |
|
| 12 |
+
| Simplified Chinese | 50 |
|
| 13 |
+
| Traditional Chinese | 50 |
|
| 14 |
+
| Chinese-English mixed | 50 |
|
| 15 |
+
| Tables | 75 |
|
| 16 |
+
| Forms | 50 |
|
| 17 |
+
| Invoices and receipts | 50 |
|
| 18 |
+
| Mathematical formulas | 30 |
|
| 19 |
+
| Multicolumn documents | 40 |
|
| 20 |
+
| Low-quality scans | 50 |
|
| 21 |
+
| Long text pages | 30 |
|
| 22 |
+
|
| 23 |
+
### Suggested Public Sources
|
| 24 |
+
|
| 25 |
+
- **PubTabNet** — table structure recognition
|
| 26 |
+
- **CORD** — consolidated receipt dataset
|
| 27 |
+
- **SROIE** — scanned receipts OCR
|
| 28 |
+
- **ICDAR 2019** — document images
|
| 29 |
+
- **CASIA** — Chinese handwritten/printed
|
| 30 |
+
- **Custom Traditional Chinese** — government forms, academic papers
|
| 31 |
+
|
| 32 |
+
### Directory Structure
|
| 33 |
+
|
| 34 |
+
```
|
| 35 |
+
benchmarks/eval_data/
|
| 36 |
+
├── images/ # Input document images (PNG/JPG)
|
| 37 |
+
├── ground_truth/ # Reference text files (same stem as image)
|
| 38 |
+
└── metadata.json # Dataset composition and source info
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
### Ground Truth Format
|
| 42 |
+
|
| 43 |
+
Each ground truth file is a plain `.txt` file containing the expected OCR output.
|
| 44 |
+
For table evaluation, include the expected markdown table structure.
|
| 45 |
+
|
| 46 |
+
### Evaluation Protocol
|
| 47 |
+
|
| 48 |
+
1. All models compared using identical:
|
| 49 |
+
- Input images (no preprocessing unless explicitly testing preprocessing)
|
| 50 |
+
- Prompt: `<image>document parsing.`
|
| 51 |
+
- Max tokens: 8192
|
| 52 |
+
- Temperature: 0.0 (deterministic)
|
| 53 |
+
- Output normalization (via `normalize_output.py`)
|
| 54 |
+
|
| 55 |
+
2. Report per-category results, not just aggregate.
|
| 56 |
+
|
| 57 |
+
3. Separate raw-input and preprocessed-input results.
|
benchmarks/evaluate_cer.py
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Character and Word Error Rate evaluation for OCR output.
|
| 2 |
+
|
| 3 |
+
Provides CER, WER, digit-specific CER, and normalized edit distance.
|
| 4 |
+
|
| 5 |
+
Usage:
|
| 6 |
+
from benchmarks.evaluate_cer import compute_cer, compute_digit_cer, evaluate_file
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import re
|
| 12 |
+
import unicodedata
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
import numpy as np
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".tiff", ".tif", ".webp", ".bmp"}
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def extract_cjk_characters(text: str) -> list[str]:
|
| 22 |
+
"""Return Chinese, Japanese, and Korean script characters from text."""
|
| 23 |
+
ranges = (
|
| 24 |
+
(0x3400, 0x4DBF), # CJK Extension A
|
| 25 |
+
(0x4E00, 0x9FFF), # CJK Unified Ideographs
|
| 26 |
+
(0xF900, 0xFAFF), # CJK Compatibility Ideographs
|
| 27 |
+
(0x20000, 0x2FA1F), # CJK supplementary extensions
|
| 28 |
+
(0x30000, 0x323AF), # CJK Extensions G and H
|
| 29 |
+
(0x3040, 0x309F), # Hiragana
|
| 30 |
+
(0x30A0, 0x30FF), # Katakana
|
| 31 |
+
(0x31F0, 0x31FF), # Katakana phonetic extensions
|
| 32 |
+
(0xFF66, 0xFF9D), # Half-width Katakana
|
| 33 |
+
(0x1B000, 0x1B16F), # Kana supplements and small kana
|
| 34 |
+
(0x1100, 0x11FF), # Hangul Jamo
|
| 35 |
+
(0x3130, 0x318F), # Hangul compatibility Jamo
|
| 36 |
+
(0xA960, 0xA97F), # Hangul Jamo Extended-A
|
| 37 |
+
(0xAC00, 0xD7AF), # Hangul syllables
|
| 38 |
+
(0xD7B0, 0xD7FF), # Hangul Jamo Extended-B
|
| 39 |
+
)
|
| 40 |
+
return [
|
| 41 |
+
char
|
| 42 |
+
for char in text
|
| 43 |
+
if any(start <= ord(char) <= end for start, end in ranges)
|
| 44 |
+
]
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def normalize_text(text: str) -> str:
|
| 48 |
+
"""Normalize text for fair comparison.
|
| 49 |
+
|
| 50 |
+
- Unicode NFC normalization
|
| 51 |
+
- Collapse whitespace
|
| 52 |
+
- Strip leading/trailing whitespace
|
| 53 |
+
"""
|
| 54 |
+
text = unicodedata.normalize("NFC", text)
|
| 55 |
+
text = re.sub(r"\s+", " ", text)
|
| 56 |
+
return text.strip()
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def edit_distance(ref: list, hyp: list) -> int:
|
| 60 |
+
"""Compute Levenshtein edit distance between two sequences."""
|
| 61 |
+
n, m = len(ref), len(hyp)
|
| 62 |
+
if n == 0:
|
| 63 |
+
return m
|
| 64 |
+
if m == 0:
|
| 65 |
+
return n
|
| 66 |
+
|
| 67 |
+
# Use two rows for memory efficiency
|
| 68 |
+
prev = list(range(m + 1))
|
| 69 |
+
curr = [0] * (m + 1)
|
| 70 |
+
|
| 71 |
+
for i in range(1, n + 1):
|
| 72 |
+
curr[0] = i
|
| 73 |
+
for j in range(1, m + 1):
|
| 74 |
+
if ref[i - 1] == hyp[j - 1]:
|
| 75 |
+
curr[j] = prev[j - 1]
|
| 76 |
+
else:
|
| 77 |
+
curr[j] = 1 + min(prev[j], curr[j - 1], prev[j - 1])
|
| 78 |
+
prev, curr = curr, prev
|
| 79 |
+
|
| 80 |
+
return prev[m]
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def compute_cer(reference: str, hypothesis: str, normalize: bool = True) -> float:
|
| 84 |
+
"""Compute Character Error Rate.
|
| 85 |
+
|
| 86 |
+
CER = edit_distance(ref_chars, hyp_chars) / len(ref_chars)
|
| 87 |
+
"""
|
| 88 |
+
if normalize:
|
| 89 |
+
reference = normalize_text(reference)
|
| 90 |
+
hypothesis = normalize_text(hypothesis)
|
| 91 |
+
|
| 92 |
+
# Remove spaces for character-level comparison
|
| 93 |
+
ref_chars = list(reference.replace(" ", ""))
|
| 94 |
+
hyp_chars = list(hypothesis.replace(" ", ""))
|
| 95 |
+
|
| 96 |
+
if len(ref_chars) == 0:
|
| 97 |
+
return 0.0 if len(hyp_chars) == 0 else 1.0
|
| 98 |
+
|
| 99 |
+
dist = edit_distance(ref_chars, hyp_chars)
|
| 100 |
+
return dist / len(ref_chars)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def compute_wer(reference: str, hypothesis: str, normalize: bool = True) -> float:
|
| 104 |
+
"""Compute Word Error Rate.
|
| 105 |
+
|
| 106 |
+
WER = edit_distance(ref_words, hyp_words) / len(ref_words)
|
| 107 |
+
"""
|
| 108 |
+
if normalize:
|
| 109 |
+
reference = normalize_text(reference)
|
| 110 |
+
hypothesis = normalize_text(hypothesis)
|
| 111 |
+
|
| 112 |
+
ref_words = reference.split()
|
| 113 |
+
hyp_words = hypothesis.split()
|
| 114 |
+
|
| 115 |
+
if len(ref_words) == 0:
|
| 116 |
+
return 0.0 if len(hyp_words) == 0 else 1.0
|
| 117 |
+
|
| 118 |
+
dist = edit_distance(ref_words, hyp_words)
|
| 119 |
+
return dist / len(ref_words)
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def compute_digit_cer(reference: str, hypothesis: str) -> float:
|
| 123 |
+
"""Compute CER only on digit characters (0-9).
|
| 124 |
+
|
| 125 |
+
Critical for financial documents, invoices, and forms.
|
| 126 |
+
"""
|
| 127 |
+
ref_digits = re.sub(r"[^0-9]", "", reference)
|
| 128 |
+
hyp_digits = re.sub(r"[^0-9]", "", hypothesis)
|
| 129 |
+
|
| 130 |
+
if len(ref_digits) == 0:
|
| 131 |
+
return 0.0 if len(hyp_digits) == 0 else 1.0
|
| 132 |
+
|
| 133 |
+
dist = edit_distance(list(ref_digits), list(hyp_digits))
|
| 134 |
+
return dist / len(ref_digits)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def compute_cjk_cer(reference: str, hypothesis: str) -> float:
|
| 138 |
+
"""Compute CER specifically for CJK characters."""
|
| 139 |
+
ref_cjk = extract_cjk_characters(reference)
|
| 140 |
+
hyp_cjk = extract_cjk_characters(hypothesis)
|
| 141 |
+
|
| 142 |
+
if len(ref_cjk) == 0:
|
| 143 |
+
return 0.0 if len(hyp_cjk) == 0 else 1.0
|
| 144 |
+
|
| 145 |
+
dist = edit_distance(ref_cjk, hyp_cjk)
|
| 146 |
+
return dist / len(ref_cjk)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def detect_repetition(text: str, ngram_size: int = 35, threshold: int = 3) -> float:
|
| 150 |
+
"""Detect repetition rate in generated text.
|
| 151 |
+
|
| 152 |
+
Returns the fraction of n-grams that appear more than `threshold` times.
|
| 153 |
+
"""
|
| 154 |
+
if not isinstance(ngram_size, int) or isinstance(ngram_size, bool) or ngram_size < 1:
|
| 155 |
+
raise ValueError("ngram_size must be a positive integer")
|
| 156 |
+
if not isinstance(threshold, int) or isinstance(threshold, bool) or threshold < 0:
|
| 157 |
+
raise ValueError("threshold must be a non-negative integer")
|
| 158 |
+
|
| 159 |
+
words = text.split()
|
| 160 |
+
if len(words) < ngram_size:
|
| 161 |
+
return 0.0
|
| 162 |
+
|
| 163 |
+
ngrams = []
|
| 164 |
+
for i in range(len(words) - ngram_size + 1):
|
| 165 |
+
ngrams.append(tuple(words[i:i + ngram_size]))
|
| 166 |
+
|
| 167 |
+
if not ngrams:
|
| 168 |
+
return 0.0
|
| 169 |
+
|
| 170 |
+
from collections import Counter
|
| 171 |
+
counts = Counter(ngrams)
|
| 172 |
+
repeated = sum(1 for c in counts.values() if c > threshold)
|
| 173 |
+
return repeated / len(counts)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def evaluate_text(reference: str, hypothesis: str) -> dict:
|
| 177 |
+
"""Evaluate a reference/hypothesis text pair."""
|
| 178 |
+
return {
|
| 179 |
+
"cer": compute_cer(reference, hypothesis),
|
| 180 |
+
"wer": compute_wer(reference, hypothesis),
|
| 181 |
+
"digit_cer": compute_digit_cer(reference, hypothesis),
|
| 182 |
+
"cjk_cer": compute_cjk_cer(reference, hypothesis),
|
| 183 |
+
"repetition_rate": detect_repetition(hypothesis),
|
| 184 |
+
"ref_length": len(reference),
|
| 185 |
+
"hyp_length": len(hypothesis),
|
| 186 |
+
"ref_digit_count": sum(char.isascii() and char.isdigit() for char in reference),
|
| 187 |
+
"ref_cjk_count": len(extract_cjk_characters(reference)),
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def evaluate_file(reference_path: str | Path, hypothesis_path: str | Path) -> dict:
|
| 192 |
+
"""Evaluate a single reference/hypothesis pair."""
|
| 193 |
+
reference = Path(reference_path).read_text(encoding="utf-8")
|
| 194 |
+
hypothesis = Path(hypothesis_path).read_text(encoding="utf-8")
|
| 195 |
+
return evaluate_text(reference, hypothesis)
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def evaluate_directory(
|
| 199 |
+
images_dir: str | Path,
|
| 200 |
+
ground_truth_dir: str | Path,
|
| 201 |
+
hypothesis_dir: str | Path,
|
| 202 |
+
) -> dict:
|
| 203 |
+
"""Evaluate all files in a directory.
|
| 204 |
+
|
| 205 |
+
Returns aggregate metrics.
|
| 206 |
+
"""
|
| 207 |
+
image_dir = Path(images_dir)
|
| 208 |
+
gt_dir = Path(ground_truth_dir)
|
| 209 |
+
hyp_dir = Path(hypothesis_dir)
|
| 210 |
+
|
| 211 |
+
for label, directory in (
|
| 212 |
+
("images", image_dir),
|
| 213 |
+
("ground truth", gt_dir),
|
| 214 |
+
("hypothesis", hyp_dir),
|
| 215 |
+
):
|
| 216 |
+
if not directory.is_dir():
|
| 217 |
+
raise NotADirectoryError(f"{label.title()} directory not found: {directory}")
|
| 218 |
+
|
| 219 |
+
image_files = sorted(
|
| 220 |
+
path
|
| 221 |
+
for path in image_dir.iterdir()
|
| 222 |
+
if path.is_file() and path.suffix.lower() in IMAGE_EXTENSIONS
|
| 223 |
+
)
|
| 224 |
+
image_stems = [path.stem for path in image_files]
|
| 225 |
+
duplicate_stems = sorted({stem for stem in image_stems if image_stems.count(stem) > 1})
|
| 226 |
+
if duplicate_stems:
|
| 227 |
+
raise ValueError(
|
| 228 |
+
"Multiple input images share the same stem: " + ", ".join(duplicate_stems)
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
results = []
|
| 232 |
+
missing_ground_truth = []
|
| 233 |
+
missing_hypotheses = []
|
| 234 |
+
for image_file in image_files:
|
| 235 |
+
gt_file = gt_dir / f"{image_file.stem}.txt"
|
| 236 |
+
if not gt_file.is_file():
|
| 237 |
+
missing_ground_truth.append(image_file.name)
|
| 238 |
+
continue
|
| 239 |
+
hyp_file = hyp_dir / gt_file.name
|
| 240 |
+
reference = gt_file.read_text(encoding="utf-8")
|
| 241 |
+
if hyp_file.is_file():
|
| 242 |
+
hypothesis = hyp_file.read_text(encoding="utf-8")
|
| 243 |
+
else:
|
| 244 |
+
# A missing prediction is an empty prediction, not a sample that
|
| 245 |
+
# can be silently removed from the accuracy denominator.
|
| 246 |
+
hypothesis = ""
|
| 247 |
+
missing_hypotheses.append(image_file.name)
|
| 248 |
+
result = evaluate_text(reference, hypothesis)
|
| 249 |
+
result["file"] = gt_file.stem
|
| 250 |
+
result["image"] = image_file.name
|
| 251 |
+
result["missing_hypothesis"] = not hyp_file.is_file()
|
| 252 |
+
results.append(result)
|
| 253 |
+
|
| 254 |
+
if not results:
|
| 255 |
+
return {
|
| 256 |
+
"error": "No images with matching ground-truth files found",
|
| 257 |
+
"num_images": len(image_files),
|
| 258 |
+
"num_samples": 0,
|
| 259 |
+
"missing_ground_truth": missing_ground_truth,
|
| 260 |
+
"missing_hypotheses": missing_hypotheses,
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
digit_results = [r["digit_cer"] for r in results if r["ref_digit_count"] > 0]
|
| 264 |
+
cjk_results = [r["cjk_cer"] for r in results if r["ref_cjk_count"] > 0]
|
| 265 |
+
|
| 266 |
+
return {
|
| 267 |
+
"num_images": len(image_files),
|
| 268 |
+
"num_samples": len(results),
|
| 269 |
+
"num_digit_samples": len(digit_results),
|
| 270 |
+
"num_cjk_samples": len(cjk_results),
|
| 271 |
+
"missing_ground_truth": missing_ground_truth,
|
| 272 |
+
"missing_hypotheses": missing_hypotheses,
|
| 273 |
+
"mean_cer": float(np.mean([r["cer"] for r in results])),
|
| 274 |
+
"mean_wer": float(np.mean([r["wer"] for r in results])),
|
| 275 |
+
"mean_digit_cer": float(np.mean(digit_results)) if digit_results else None,
|
| 276 |
+
"mean_cjk_cer": float(np.mean(cjk_results)) if cjk_results else None,
|
| 277 |
+
"mean_repetition_rate": float(np.mean([r["repetition_rate"] for r in results])),
|
| 278 |
+
"per_file": results,
|
| 279 |
+
}
|
benchmarks/evaluate_tables.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Table structure evaluation (TEDS-like scoring).
|
| 2 |
+
|
| 3 |
+
Provides a simplified Tree-Edit-Distance-based Similarity score for tables
|
| 4 |
+
by comparing HTML table structures.
|
| 5 |
+
|
| 6 |
+
Usage:
|
| 7 |
+
from benchmarks.evaluate_tables import compute_table_score, evaluate_tables
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import re
|
| 13 |
+
from html.parser import HTMLParser
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def _split_markdown_row(line: str) -> list[str]:
|
| 18 |
+
"""Split a Markdown row, honoring optional outer and escaped pipes."""
|
| 19 |
+
stripped = line.strip()
|
| 20 |
+
if stripped.startswith("|"):
|
| 21 |
+
stripped = stripped[1:]
|
| 22 |
+
if stripped.endswith("|") and not stripped.endswith(r"\|"):
|
| 23 |
+
stripped = stripped[:-1]
|
| 24 |
+
return [
|
| 25 |
+
cell.replace(r"\|", "|").strip()
|
| 26 |
+
for cell in re.split(r"(?<!\\)\|", stripped)
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _is_separator_row(line: str) -> bool:
|
| 31 |
+
cells = _split_markdown_row(line)
|
| 32 |
+
return bool(cells) and all(re.fullmatch(r":?-{3,}:?", cell) for cell in cells)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _extract_markdown_tables_with_positions(text: str) -> list[tuple[int, str]]:
|
| 36 |
+
"""Extract Markdown tables together with their character offsets."""
|
| 37 |
+
lines = text.split("\n")
|
| 38 |
+
offsets = []
|
| 39 |
+
offset = 0
|
| 40 |
+
for line in lines:
|
| 41 |
+
offsets.append(offset)
|
| 42 |
+
offset += len(line) + 1
|
| 43 |
+
|
| 44 |
+
tables: list[tuple[int, str]] = []
|
| 45 |
+
index = 0
|
| 46 |
+
while index + 1 < len(lines):
|
| 47 |
+
table_start = index
|
| 48 |
+
header = lines[index].strip()
|
| 49 |
+
separator = lines[index + 1].strip()
|
| 50 |
+
if "|" not in header or not _is_separator_row(separator):
|
| 51 |
+
index += 1
|
| 52 |
+
continue
|
| 53 |
+
|
| 54 |
+
header_cells = _split_markdown_row(header)
|
| 55 |
+
separator_cells = _split_markdown_row(separator)
|
| 56 |
+
if len(header_cells) != len(separator_cells):
|
| 57 |
+
index += 1
|
| 58 |
+
continue
|
| 59 |
+
|
| 60 |
+
current_table = [header, separator]
|
| 61 |
+
index += 2
|
| 62 |
+
while index < len(lines):
|
| 63 |
+
row = lines[index].strip()
|
| 64 |
+
if not row or "|" not in row:
|
| 65 |
+
break
|
| 66 |
+
current_table.append(row)
|
| 67 |
+
index += 1
|
| 68 |
+
tables.append((offsets[table_start], "\n".join(current_table)))
|
| 69 |
+
|
| 70 |
+
return tables
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def extract_tables_from_markdown(text: str) -> list[str]:
|
| 74 |
+
"""Extract markdown tables from OCR output."""
|
| 75 |
+
return [table for _, table in _extract_markdown_tables_with_positions(text)]
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def extract_tables_from_html(text: str) -> list[str]:
|
| 79 |
+
"""Extract HTML tables from OCR output."""
|
| 80 |
+
return [table for _, table in _extract_html_tables_with_positions(text)]
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def _extract_html_tables_with_positions(text: str) -> list[tuple[int, str]]:
|
| 84 |
+
"""Extract balanced outer HTML tables, preserving nested table markup."""
|
| 85 |
+
tag_pattern = re.compile(r"</?table\b[^>]*>", re.IGNORECASE)
|
| 86 |
+
tables: list[tuple[int, str]] = []
|
| 87 |
+
depth = 0
|
| 88 |
+
start: int | None = None
|
| 89 |
+
for match in tag_pattern.finditer(text):
|
| 90 |
+
if re.match(r"<\s*/", match.group(0)):
|
| 91 |
+
if depth == 0:
|
| 92 |
+
continue
|
| 93 |
+
depth -= 1
|
| 94 |
+
if depth == 0 and start is not None:
|
| 95 |
+
tables.append((start, text[start:match.end()]))
|
| 96 |
+
start = None
|
| 97 |
+
else:
|
| 98 |
+
if depth == 0:
|
| 99 |
+
start = match.start()
|
| 100 |
+
depth += 1
|
| 101 |
+
return tables
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _extract_tables_in_document_order(text: str) -> list[str]:
|
| 105 |
+
"""Extract Markdown and HTML tables without reordering their occurrences."""
|
| 106 |
+
html_tables = _extract_html_tables_with_positions(text)
|
| 107 |
+
html_ranges = [(start, start + len(table)) for start, table in html_tables]
|
| 108 |
+
located = [
|
| 109 |
+
(start, table)
|
| 110 |
+
for start, table in _extract_markdown_tables_with_positions(text)
|
| 111 |
+
if not any(begin <= start < end for begin, end in html_ranges)
|
| 112 |
+
]
|
| 113 |
+
located.extend(html_tables)
|
| 114 |
+
located.sort(key=lambda item: item[0])
|
| 115 |
+
return [table for _, table in located]
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
class _HTMLTableParser(HTMLParser):
|
| 119 |
+
"""Collect rows and cells from one HTML table without external parsers."""
|
| 120 |
+
|
| 121 |
+
def __init__(self):
|
| 122 |
+
super().__init__(convert_charrefs=True)
|
| 123 |
+
self.table_depth = 0
|
| 124 |
+
self.rows: list[list[str]] = []
|
| 125 |
+
self._row: list[str] | None = None
|
| 126 |
+
self._cell_parts: list[str] | None = None
|
| 127 |
+
|
| 128 |
+
def handle_starttag(self, tag: str, attrs):
|
| 129 |
+
tag = tag.lower()
|
| 130 |
+
if tag == "table":
|
| 131 |
+
self.table_depth += 1
|
| 132 |
+
elif self.table_depth == 1 and tag == "tr":
|
| 133 |
+
self._row = []
|
| 134 |
+
elif self.table_depth == 1 and tag in {"td", "th"} and self._row is not None:
|
| 135 |
+
self._cell_parts = []
|
| 136 |
+
elif self.table_depth == 1 and tag == "br" and self._cell_parts is not None:
|
| 137 |
+
self._cell_parts.append("\n")
|
| 138 |
+
|
| 139 |
+
def handle_data(self, data: str):
|
| 140 |
+
if self.table_depth == 1 and self._cell_parts is not None:
|
| 141 |
+
self._cell_parts.append(data)
|
| 142 |
+
|
| 143 |
+
def handle_endtag(self, tag: str):
|
| 144 |
+
tag = tag.lower()
|
| 145 |
+
if self.table_depth == 1 and tag in {"td", "th"}:
|
| 146 |
+
if self._row is not None and self._cell_parts is not None:
|
| 147 |
+
value = re.sub(r"\s+", " ", "".join(self._cell_parts)).strip()
|
| 148 |
+
self._row.append(value)
|
| 149 |
+
self._cell_parts = None
|
| 150 |
+
elif self.table_depth == 1 and tag == "tr":
|
| 151 |
+
if self._row is not None and self._row:
|
| 152 |
+
self.rows.append(self._row)
|
| 153 |
+
self._row = None
|
| 154 |
+
self._cell_parts = None
|
| 155 |
+
elif tag == "table" and self.table_depth:
|
| 156 |
+
self.table_depth -= 1
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def _normalize_html_table(table_text: str) -> dict:
|
| 160 |
+
parser = _HTMLTableParser()
|
| 161 |
+
parser.feed(table_text)
|
| 162 |
+
parser.close()
|
| 163 |
+
rows = parser.rows
|
| 164 |
+
if not rows:
|
| 165 |
+
return {"num_rows": 0, "num_cols": 0, "cells": []}
|
| 166 |
+
return {
|
| 167 |
+
"num_rows": len(rows),
|
| 168 |
+
"num_cols": max(len(row) for row in rows),
|
| 169 |
+
"cells": [cell for row in rows for cell in row],
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def normalize_table(table_text: str) -> dict:
|
| 174 |
+
"""Parse a markdown table into a normalized structure.
|
| 175 |
+
|
| 176 |
+
Returns dict with: num_rows, num_cols, cells (flattened list).
|
| 177 |
+
"""
|
| 178 |
+
if re.search(r"<table\b", table_text, re.IGNORECASE):
|
| 179 |
+
return _normalize_html_table(table_text)
|
| 180 |
+
|
| 181 |
+
lines = [l.strip() for l in table_text.strip().split("\n") if l.strip()]
|
| 182 |
+
|
| 183 |
+
# Filter out the Markdown header separator.
|
| 184 |
+
data_lines = [line for line in lines if not _is_separator_row(line)]
|
| 185 |
+
|
| 186 |
+
rows = []
|
| 187 |
+
for line in data_lines:
|
| 188 |
+
rows.append(_split_markdown_row(line))
|
| 189 |
+
|
| 190 |
+
if not rows:
|
| 191 |
+
return {"num_rows": 0, "num_cols": 0, "cells": []}
|
| 192 |
+
|
| 193 |
+
num_cols = max(len(r) for r in rows) if rows else 0
|
| 194 |
+
cells = []
|
| 195 |
+
for row in rows:
|
| 196 |
+
cells.extend(row)
|
| 197 |
+
|
| 198 |
+
return {
|
| 199 |
+
"num_rows": len(rows),
|
| 200 |
+
"num_cols": num_cols,
|
| 201 |
+
"cells": cells,
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def compute_table_score(reference_table: str, hypothesis_table: str) -> float:
|
| 206 |
+
"""Compute a simplified TEDS-like score between two tables.
|
| 207 |
+
|
| 208 |
+
Score is based on:
|
| 209 |
+
- Structure match (rows x cols): 40% weight
|
| 210 |
+
- Cell content match: 60% weight
|
| 211 |
+
|
| 212 |
+
Returns score in [0, 1] where 1 = perfect match.
|
| 213 |
+
"""
|
| 214 |
+
ref = normalize_table(reference_table)
|
| 215 |
+
hyp = normalize_table(hypothesis_table)
|
| 216 |
+
|
| 217 |
+
if ref["num_rows"] == 0 and hyp["num_rows"] == 0:
|
| 218 |
+
return 1.0
|
| 219 |
+
if ref["num_rows"] == 0 or hyp["num_rows"] == 0:
|
| 220 |
+
return 0.0
|
| 221 |
+
|
| 222 |
+
# Structure score
|
| 223 |
+
row_match = 1.0 - abs(ref["num_rows"] - hyp["num_rows"]) / max(ref["num_rows"], hyp["num_rows"])
|
| 224 |
+
col_match = 1.0 - abs(ref["num_cols"] - hyp["num_cols"]) / max(ref["num_cols"], hyp["num_cols"])
|
| 225 |
+
structure_score = (row_match + col_match) / 2
|
| 226 |
+
|
| 227 |
+
# Cell content score (compare flattened cells)
|
| 228 |
+
ref_cells = ref["cells"]
|
| 229 |
+
hyp_cells = hyp["cells"]
|
| 230 |
+
|
| 231 |
+
if not ref_cells:
|
| 232 |
+
return structure_score * 0.4
|
| 233 |
+
|
| 234 |
+
# Simple cell-by-cell comparison
|
| 235 |
+
max_len = max(len(ref_cells), len(hyp_cells))
|
| 236 |
+
matches = 0
|
| 237 |
+
for i in range(min(len(ref_cells), len(hyp_cells))):
|
| 238 |
+
# Normalize whitespace for comparison
|
| 239 |
+
rc = re.sub(r"\s+", " ", ref_cells[i]).strip().lower()
|
| 240 |
+
hc = re.sub(r"\s+", " ", hyp_cells[i]).strip().lower()
|
| 241 |
+
if rc == hc:
|
| 242 |
+
matches += 1
|
| 243 |
+
elif rc and hc and (rc in hc or hc in rc):
|
| 244 |
+
matches += 0.5
|
| 245 |
+
|
| 246 |
+
cell_score = matches / max_len if max_len > 0 else 0.0
|
| 247 |
+
|
| 248 |
+
# Weighted combination
|
| 249 |
+
return 0.4 * structure_score + 0.6 * cell_score
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
def evaluate_tables(reference_text: str, hypothesis_text: str) -> dict:
|
| 253 |
+
"""Evaluate table extraction quality between reference and hypothesis.
|
| 254 |
+
|
| 255 |
+
Returns dict with: num_ref_tables, num_hyp_tables, mean_score, per_table_scores.
|
| 256 |
+
"""
|
| 257 |
+
ref_tables = _extract_tables_in_document_order(reference_text)
|
| 258 |
+
hyp_tables = _extract_tables_in_document_order(hypothesis_text)
|
| 259 |
+
|
| 260 |
+
if not ref_tables:
|
| 261 |
+
return {
|
| 262 |
+
"num_ref_tables": 0,
|
| 263 |
+
"num_hyp_tables": len(hyp_tables),
|
| 264 |
+
"mean_score": 1.0 if not hyp_tables else 0.0,
|
| 265 |
+
"per_table_scores": [0.0] * len(hyp_tables),
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
# Match tables by position (simplified)
|
| 269 |
+
scores = []
|
| 270 |
+
for i, ref_table in enumerate(ref_tables):
|
| 271 |
+
if i < len(hyp_tables):
|
| 272 |
+
score = compute_table_score(ref_table, hyp_tables[i])
|
| 273 |
+
else:
|
| 274 |
+
score = 0.0 # Missing table
|
| 275 |
+
scores.append(score)
|
| 276 |
+
|
| 277 |
+
# Hallucinated extra tables must count against the result as well.
|
| 278 |
+
scores.extend([0.0] * max(0, len(hyp_tables) - len(ref_tables)))
|
| 279 |
+
|
| 280 |
+
return {
|
| 281 |
+
"num_ref_tables": len(ref_tables),
|
| 282 |
+
"num_hyp_tables": len(hyp_tables),
|
| 283 |
+
"mean_score": float(sum(scores) / len(scores)) if scores else 0.0,
|
| 284 |
+
"per_table_scores": scores,
|
| 285 |
+
}
|
benchmarks/normalize_output.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Normalize OCR output for fair comparison.
|
| 2 |
+
|
| 3 |
+
Strips model-specific artifacts, normalizes whitespace, and removes
|
| 4 |
+
special tokens before evaluation.
|
| 5 |
+
|
| 6 |
+
Usage:
|
| 7 |
+
python benchmarks/normalize_output.py input.txt output.txt
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import argparse
|
| 13 |
+
import re
|
| 14 |
+
import unicodedata
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def normalize_ocr_output(text: str) -> str:
|
| 19 |
+
"""Normalize OCR output for evaluation.
|
| 20 |
+
|
| 21 |
+
Steps:
|
| 22 |
+
1. Remove special tokens (<|det|>, <|/det|>, <|grounding|>, etc.)
|
| 23 |
+
2. Unicode NFC normalization
|
| 24 |
+
3. Normalize whitespace (collapse multiple spaces/newlines)
|
| 25 |
+
4. Strip leading/trailing whitespace
|
| 26 |
+
5. Normalize common OCR artifacts
|
| 27 |
+
"""
|
| 28 |
+
if not isinstance(text, str):
|
| 29 |
+
raise TypeError("text must be a string")
|
| 30 |
+
|
| 31 |
+
# Tokenizer-visible whitespace markers occasionally survive decoding.
|
| 32 |
+
text = text.replace("Ġ", " ").replace("Ċ", "\n")
|
| 33 |
+
|
| 34 |
+
# Remove grounding coordinates only when they are part of a detection
|
| 35 |
+
# span. A blanket ``[digits]`` removal corrupts legitimate text such as
|
| 36 |
+
# citations and list markers.
|
| 37 |
+
number = r"-?\d+(?:\.\d+)?"
|
| 38 |
+
coordinate_pattern = re.compile(
|
| 39 |
+
rf"\[\s*{number}\s*,\s*{number}\s*,\s*{number}\s*,\s*{number}\s*\]"
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
# In the older ref/det form, the ref span is recognized content and should
|
| 43 |
+
# be retained while the following coordinate-only det span is removed.
|
| 44 |
+
text = re.sub(
|
| 45 |
+
r"<\|ref\|>(.*?)<\|/ref\|>\s*<\|det\|>.*?<\|/det\|>",
|
| 46 |
+
lambda match: match.group(1),
|
| 47 |
+
text,
|
| 48 |
+
flags=re.DOTALL,
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
def clean_detection(match: re.Match) -> str:
|
| 52 |
+
inner = match.group(1)
|
| 53 |
+
# Current output uses ``<|det|>layout-label [box]<|/det|>content``.
|
| 54 |
+
# Both the layout label and coordinates are metadata, not OCR text.
|
| 55 |
+
return "" if coordinate_pattern.search(inner) else inner
|
| 56 |
+
|
| 57 |
+
text = re.sub(
|
| 58 |
+
r"<\|det\|>(.*?)<\|/det\|>",
|
| 59 |
+
clean_detection,
|
| 60 |
+
text,
|
| 61 |
+
flags=re.DOTALL,
|
| 62 |
+
)
|
| 63 |
+
# Some decoded outputs omit the first opening <|det|> token.
|
| 64 |
+
text = re.sub(
|
| 65 |
+
rf"(?m)(^|\n)[^\n<]*?{coordinate_pattern.pattern}"
|
| 66 |
+
rf"(?=\s*<\|/det\|>)",
|
| 67 |
+
lambda match: match.group(1),
|
| 68 |
+
text,
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
# Remove special tokens
|
| 72 |
+
text = re.sub(r"<\|[^|]*\|>", "", text)
|
| 73 |
+
text = re.sub(r"<|[^|]*|>", "", text)
|
| 74 |
+
text = re.sub(r"<PAGE>", "\n", text, flags=re.IGNORECASE)
|
| 75 |
+
text = re.sub(r"<image>", "", text)
|
| 76 |
+
|
| 77 |
+
# Unicode normalization
|
| 78 |
+
text = unicodedata.normalize("NFC", text)
|
| 79 |
+
|
| 80 |
+
# Normalize different types of whitespace
|
| 81 |
+
text = text.replace("\r\n", "\n").replace("\r", "\n")
|
| 82 |
+
|
| 83 |
+
# Collapse multiple blank lines to max 2
|
| 84 |
+
text = re.sub(r"\n{3,}", "\n\n", text)
|
| 85 |
+
|
| 86 |
+
# Collapse multiple spaces (but preserve single newlines)
|
| 87 |
+
text = re.sub(r"[^\S\n]+", " ", text)
|
| 88 |
+
|
| 89 |
+
# Strip each line
|
| 90 |
+
lines = [line.strip() for line in text.split("\n")]
|
| 91 |
+
text = "\n".join(lines)
|
| 92 |
+
|
| 93 |
+
# Whitespace-only lines become empty only after the per-line strip above,
|
| 94 |
+
# so enforce the blank-line limit again afterwards.
|
| 95 |
+
text = re.sub(r"\n{3,}", "\n\n", text)
|
| 96 |
+
|
| 97 |
+
# Strip overall
|
| 98 |
+
text = text.strip()
|
| 99 |
+
|
| 100 |
+
return text
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def normalize_for_digit_comparison(text: str) -> str:
|
| 104 |
+
"""Extract only digits and decimal points for numeric comparison."""
|
| 105 |
+
return re.sub(r"[^0-9.]", "", text)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def main():
|
| 109 |
+
parser = argparse.ArgumentParser(description="Normalize OCR output for evaluation")
|
| 110 |
+
parser.add_argument("input", type=Path, help="Raw OCR text file")
|
| 111 |
+
parser.add_argument("output", type=Path, help="Normalized output file")
|
| 112 |
+
args = parser.parse_args()
|
| 113 |
+
|
| 114 |
+
input_path = args.input
|
| 115 |
+
output_path = args.output
|
| 116 |
+
|
| 117 |
+
text = input_path.read_text(encoding="utf-8")
|
| 118 |
+
normalized = normalize_ocr_output(text)
|
| 119 |
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 120 |
+
output_path.write_text(normalized, encoding="utf-8")
|
| 121 |
+
|
| 122 |
+
print(f"Normalized: {input_path} -> {output_path}")
|
| 123 |
+
print(f" Input: {len(text)} chars")
|
| 124 |
+
print(f" Output: {len(normalized)} chars")
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
if __name__ == "__main__":
|
| 128 |
+
main()
|
benchmarks/rswa_validation.py
ADDED
|
@@ -0,0 +1,500 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""R-SWA (Reference Sliding Window Attention) cache validation.
|
| 2 |
+
|
| 3 |
+
Instruments MLX KV-cache behavior at increasing output lengths to verify
|
| 4 |
+
whether the model's R-SWA mechanism properly bounds cache growth.
|
| 5 |
+
|
| 6 |
+
Usage:
|
| 7 |
+
python benchmarks/rswa_validation.py \
|
| 8 |
+
--model-path AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8 \
|
| 9 |
+
--image test_image.jpg \
|
| 10 |
+
--output benchmarks/results/rswa_results.json
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import json
|
| 17 |
+
import platform
|
| 18 |
+
import sys
|
| 19 |
+
import time
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
|
| 22 |
+
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# Output lengths to test
|
| 26 |
+
TEST_LENGTHS = [512, 2048, 4096, 8192, 16384]
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class MinGeneratedTokensProcessor:
|
| 30 |
+
"""Suppress EOS until a requested number of new tokens has been sampled.
|
| 31 |
+
|
| 32 |
+
``mlx-vlm`` passes prompt tokens to logits processors as well as generated
|
| 33 |
+
tokens. The first observed token count is therefore recorded as the prompt
|
| 34 |
+
length, keeping this stress-test control independent of prompt tokenization.
|
| 35 |
+
A fresh processor must be constructed for every generation call.
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
def __init__(self, min_new_tokens: int, eos_token_ids: list[int]):
|
| 39 |
+
if (
|
| 40 |
+
not isinstance(min_new_tokens, int)
|
| 41 |
+
or isinstance(min_new_tokens, bool)
|
| 42 |
+
or min_new_tokens < 1
|
| 43 |
+
):
|
| 44 |
+
raise ValueError("min_new_tokens must be a positive integer")
|
| 45 |
+
normalized_ids = sorted({
|
| 46 |
+
int(token_id)
|
| 47 |
+
for token_id in eos_token_ids
|
| 48 |
+
if isinstance(token_id, int) and not isinstance(token_id, bool)
|
| 49 |
+
})
|
| 50 |
+
if not normalized_ids:
|
| 51 |
+
raise ValueError("At least one integer EOS token ID is required")
|
| 52 |
+
self.min_new_tokens = min_new_tokens
|
| 53 |
+
self.eos_token_ids = normalized_ids
|
| 54 |
+
self._prompt_token_count: int | None = None
|
| 55 |
+
|
| 56 |
+
def __call__(self, tokens, logits):
|
| 57 |
+
sequence = tokens.tolist() if hasattr(tokens, "tolist") else list(tokens)
|
| 58 |
+
if sequence and isinstance(sequence[0], list):
|
| 59 |
+
sequence = sequence[0]
|
| 60 |
+
if self._prompt_token_count is None:
|
| 61 |
+
self._prompt_token_count = len(sequence)
|
| 62 |
+
generated_tokens = max(0, len(sequence) - self._prompt_token_count)
|
| 63 |
+
if generated_tokens < self.min_new_tokens:
|
| 64 |
+
logits[:, self.eos_token_ids] = float("-inf")
|
| 65 |
+
return logits
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def collect_eos_token_ids(model, processor) -> list[int]:
|
| 69 |
+
"""Collect EOS IDs from model config and the loaded tokenizer wrapper."""
|
| 70 |
+
values = []
|
| 71 |
+
model_config = getattr(model, "config", None)
|
| 72 |
+
values.append(getattr(model_config, "eos_token_id", None))
|
| 73 |
+
|
| 74 |
+
tokenizer = getattr(processor, "tokenizer", processor)
|
| 75 |
+
values.append(getattr(tokenizer, "eos_token_ids", None))
|
| 76 |
+
values.append(getattr(tokenizer, "eos_token_id", None))
|
| 77 |
+
stopping_criteria = getattr(tokenizer, "stopping_criteria", None)
|
| 78 |
+
values.append(getattr(stopping_criteria, "eos_token_ids", None))
|
| 79 |
+
|
| 80 |
+
normalized: set[int] = set()
|
| 81 |
+
for value in values:
|
| 82 |
+
candidates = value if isinstance(value, (list, tuple, set)) else [value]
|
| 83 |
+
for candidate in candidates:
|
| 84 |
+
if isinstance(candidate, int) and not isinstance(candidate, bool):
|
| 85 |
+
normalized.add(candidate)
|
| 86 |
+
if not normalized:
|
| 87 |
+
raise ValueError("Could not determine EOS token IDs from model or processor")
|
| 88 |
+
return sorted(normalized)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def detect_repetition_rate(text: str, ngram_size: int = 35, threshold: int = 3) -> float:
|
| 92 |
+
"""Detect fraction of repeated n-grams in output."""
|
| 93 |
+
if not isinstance(ngram_size, int) or isinstance(ngram_size, bool) or ngram_size < 1:
|
| 94 |
+
raise ValueError("ngram_size must be a positive integer")
|
| 95 |
+
if not isinstance(threshold, int) or isinstance(threshold, bool) or threshold < 0:
|
| 96 |
+
raise ValueError("threshold must be a non-negative integer")
|
| 97 |
+
words = text.split()
|
| 98 |
+
if len(words) < ngram_size:
|
| 99 |
+
return 0.0
|
| 100 |
+
|
| 101 |
+
from collections import Counter
|
| 102 |
+
ngrams = [tuple(words[i:i+ngram_size]) for i in range(len(words) - ngram_size + 1)]
|
| 103 |
+
counts = Counter(ngrams)
|
| 104 |
+
repeated = sum(1 for c in counts.values() if c > threshold)
|
| 105 |
+
return repeated / len(counts) if counts else 0.0
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def analyze_cache_behavior(results: list[dict]) -> tuple[bool | None, str]:
|
| 109 |
+
"""Conservatively classify cache growth from successful length-limited runs.
|
| 110 |
+
|
| 111 |
+
Returns ``None`` when the generated token counts do not span enough lengths
|
| 112 |
+
to distinguish a plateau from ordinary noise or linear growth.
|
| 113 |
+
"""
|
| 114 |
+
successful = [
|
| 115 |
+
result
|
| 116 |
+
for result in results
|
| 117 |
+
if result.get("status") == "success"
|
| 118 |
+
and result.get("tokens_generated", 0) > 0
|
| 119 |
+
and result.get("tokens_generated_reliable", True)
|
| 120 |
+
and result.get("memory_growth_mb") is not None
|
| 121 |
+
]
|
| 122 |
+
# Collapse duplicate actual token counts; raising max_tokens is not useful
|
| 123 |
+
# evidence when the model naturally stops at the same length each time.
|
| 124 |
+
by_tokens: dict[int, float] = {}
|
| 125 |
+
for result in successful:
|
| 126 |
+
tokens = int(result["tokens_generated"])
|
| 127 |
+
by_tokens[tokens] = max(by_tokens.get(tokens, 0.0), result["memory_growth_mb"])
|
| 128 |
+
points = sorted(by_tokens.items())
|
| 129 |
+
|
| 130 |
+
if len(points) < 3:
|
| 131 |
+
return None, "Need at least three distinct generated lengths"
|
| 132 |
+
if points[-1][0] < points[0][0] * 2:
|
| 133 |
+
return None, "Generated token counts span less than 2x"
|
| 134 |
+
|
| 135 |
+
# Peak-memory readings can fluctuate slightly, so enforce their cumulative
|
| 136 |
+
# maximum before comparing early and late slopes.
|
| 137 |
+
monotonic_memory = []
|
| 138 |
+
running_peak = 0.0
|
| 139 |
+
for tokens, memory in points:
|
| 140 |
+
running_peak = max(running_peak, float(memory))
|
| 141 |
+
monotonic_memory.append((tokens, running_peak))
|
| 142 |
+
|
| 143 |
+
midpoint = len(monotonic_memory) // 2
|
| 144 |
+
first_tokens, first_memory = monotonic_memory[0]
|
| 145 |
+
middle_tokens, middle_memory = monotonic_memory[midpoint]
|
| 146 |
+
last_tokens, last_memory = monotonic_memory[-1]
|
| 147 |
+
early_span = middle_tokens - first_tokens
|
| 148 |
+
late_span = last_tokens - middle_tokens
|
| 149 |
+
if early_span <= 0 or late_span <= 0:
|
| 150 |
+
return None, "Generated lengths are not sufficiently distinct"
|
| 151 |
+
|
| 152 |
+
total_memory_change = last_memory - first_memory
|
| 153 |
+
# A wide token span with <=64 MB of additional peak memory is direct
|
| 154 |
+
# plateau evidence even if the early slope is effectively zero.
|
| 155 |
+
if last_tokens >= first_tokens * 4 and total_memory_change <= 64.0:
|
| 156 |
+
return True, "Peak memory stayed within 64 MB across a >=4x token span"
|
| 157 |
+
|
| 158 |
+
early_slope = (middle_memory - first_memory) / early_span
|
| 159 |
+
late_slope = (last_memory - middle_memory) / late_span
|
| 160 |
+
if early_slope <= 0:
|
| 161 |
+
return None, "Memory variation is too small to estimate a growth trend"
|
| 162 |
+
|
| 163 |
+
if late_slope <= early_slope * 0.25:
|
| 164 |
+
return True, "Late memory-growth slope is <=25% of the early slope"
|
| 165 |
+
return False, "Memory growth does not plateau at longer generated lengths"
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def run_rswa_validation(
|
| 169 |
+
model_path: str,
|
| 170 |
+
image_path: str,
|
| 171 |
+
prompt: str = "<image>document parsing.",
|
| 172 |
+
test_lengths: list[int] | None = None,
|
| 173 |
+
force_min_tokens: int | None = None,
|
| 174 |
+
) -> dict:
|
| 175 |
+
"""Run R-SWA cache validation at increasing output lengths.
|
| 176 |
+
|
| 177 |
+
Measures:
|
| 178 |
+
- Peak memory at each output length
|
| 179 |
+
- Decode TPS in first vs last quarter
|
| 180 |
+
- Cache growth behavior
|
| 181 |
+
- Repetition rate
|
| 182 |
+
- Completion status
|
| 183 |
+
"""
|
| 184 |
+
if test_lengths is None:
|
| 185 |
+
test_lengths = list(TEST_LENGTHS)
|
| 186 |
+
else:
|
| 187 |
+
test_lengths = list(test_lengths)
|
| 188 |
+
if not Path(image_path).is_file():
|
| 189 |
+
raise FileNotFoundError(f"Test image not found: {image_path}")
|
| 190 |
+
if not isinstance(prompt, str) or prompt.count("<image>") != 1:
|
| 191 |
+
raise ValueError("prompt must contain exactly one literal '<image>' token")
|
| 192 |
+
if not test_lengths or any(
|
| 193 |
+
not isinstance(length, int) or isinstance(length, bool) or length < 1
|
| 194 |
+
for length in test_lengths
|
| 195 |
+
):
|
| 196 |
+
raise ValueError("test_lengths must contain positive integers")
|
| 197 |
+
if len(set(test_lengths)) != len(test_lengths):
|
| 198 |
+
raise ValueError("test_lengths must not contain duplicates")
|
| 199 |
+
if test_lengths != sorted(test_lengths):
|
| 200 |
+
raise ValueError("test_lengths must be in increasing order")
|
| 201 |
+
if len(test_lengths) < 3:
|
| 202 |
+
raise ValueError("test_lengths must contain at least three lengths")
|
| 203 |
+
if 8192 not in test_lengths:
|
| 204 |
+
raise ValueError("test_lengths must include 8192 for the required 8K check")
|
| 205 |
+
if test_lengths[-1] < test_lengths[0] * 2:
|
| 206 |
+
raise ValueError("test_lengths must span at least 2x")
|
| 207 |
+
if force_min_tokens is not None and (
|
| 208 |
+
not isinstance(force_min_tokens, int)
|
| 209 |
+
or isinstance(force_min_tokens, bool)
|
| 210 |
+
or force_min_tokens < 1
|
| 211 |
+
):
|
| 212 |
+
raise ValueError("force_min_tokens must be a positive integer or None")
|
| 213 |
+
|
| 214 |
+
from mlx_vlm import load, generate
|
| 215 |
+
import mlx.core as mx
|
| 216 |
+
|
| 217 |
+
print(f"Loading model: {model_path}")
|
| 218 |
+
model, processor = load(model_path)
|
| 219 |
+
eos_token_ids = (
|
| 220 |
+
collect_eos_token_ids(model, processor)
|
| 221 |
+
if force_min_tokens is not None
|
| 222 |
+
else []
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
baseline_memory = mx.get_active_memory() / 1e6
|
| 226 |
+
print(f"Baseline memory: {baseline_memory:.0f} MB")
|
| 227 |
+
|
| 228 |
+
results = []
|
| 229 |
+
|
| 230 |
+
for max_tokens in test_lengths:
|
| 231 |
+
print(f"\n--- Testing max_tokens={max_tokens} ---")
|
| 232 |
+
|
| 233 |
+
mx.clear_cache()
|
| 234 |
+
mem_before = mx.get_active_memory() / 1e6
|
| 235 |
+
mx.reset_peak_memory()
|
| 236 |
+
start = time.perf_counter()
|
| 237 |
+
|
| 238 |
+
try:
|
| 239 |
+
forced_minimum = (
|
| 240 |
+
min(force_min_tokens, max_tokens)
|
| 241 |
+
if force_min_tokens is not None
|
| 242 |
+
else None
|
| 243 |
+
)
|
| 244 |
+
logits_processors = (
|
| 245 |
+
[MinGeneratedTokensProcessor(forced_minimum, eos_token_ids)]
|
| 246 |
+
if forced_minimum is not None
|
| 247 |
+
else None
|
| 248 |
+
)
|
| 249 |
+
response = generate(
|
| 250 |
+
model, processor,
|
| 251 |
+
prompt=prompt,
|
| 252 |
+
image=[image_path],
|
| 253 |
+
max_tokens=max_tokens,
|
| 254 |
+
temperature=0.0,
|
| 255 |
+
logits_processors=logits_processors,
|
| 256 |
+
verbose=False,
|
| 257 |
+
)
|
| 258 |
+
elapsed = time.perf_counter() - start
|
| 259 |
+
peak_memory = (
|
| 260 |
+
response.get("peak_memory")
|
| 261 |
+
if isinstance(response, dict)
|
| 262 |
+
else getattr(response, "peak_memory", None)
|
| 263 |
+
)
|
| 264 |
+
mem_after = (
|
| 265 |
+
float(peak_memory) * 1000
|
| 266 |
+
if peak_memory is not None and float(peak_memory) > 0
|
| 267 |
+
else mx.get_peak_memory() / 1e6
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
# Extract text
|
| 271 |
+
if isinstance(response, str):
|
| 272 |
+
text = response
|
| 273 |
+
tokens_generated = len(text.split())
|
| 274 |
+
token_count_source = "whitespace estimate"
|
| 275 |
+
tokens_reliable = False
|
| 276 |
+
elif hasattr(response, "text"):
|
| 277 |
+
text = response.text
|
| 278 |
+
if not isinstance(text, str):
|
| 279 |
+
raise TypeError("mlx-vlm returned a non-string text field")
|
| 280 |
+
raw_tokens = getattr(
|
| 281 |
+
response,
|
| 282 |
+
"generation_tokens",
|
| 283 |
+
getattr(response, "tokens_generated", None),
|
| 284 |
+
)
|
| 285 |
+
if raw_tokens is None:
|
| 286 |
+
tokens_generated = len(text.split())
|
| 287 |
+
token_count_source = "whitespace estimate"
|
| 288 |
+
tokens_reliable = False
|
| 289 |
+
else:
|
| 290 |
+
tokens_generated = raw_tokens
|
| 291 |
+
token_count_source = "mlx-vlm token count"
|
| 292 |
+
tokens_reliable = True
|
| 293 |
+
elif isinstance(response, dict):
|
| 294 |
+
text = response.get("text", "")
|
| 295 |
+
if not isinstance(text, str):
|
| 296 |
+
raise TypeError("mlx-vlm returned a non-string text field")
|
| 297 |
+
raw_tokens = response.get(
|
| 298 |
+
"generation_tokens", response.get("tokens_generated")
|
| 299 |
+
)
|
| 300 |
+
if raw_tokens is None:
|
| 301 |
+
tokens_generated = len(text.split())
|
| 302 |
+
token_count_source = "whitespace estimate"
|
| 303 |
+
tokens_reliable = False
|
| 304 |
+
else:
|
| 305 |
+
tokens_generated = raw_tokens
|
| 306 |
+
token_count_source = "mlx-vlm token count"
|
| 307 |
+
tokens_reliable = True
|
| 308 |
+
else:
|
| 309 |
+
text = str(response)
|
| 310 |
+
tokens_generated = len(text.split())
|
| 311 |
+
token_count_source = "whitespace estimate"
|
| 312 |
+
tokens_reliable = False
|
| 313 |
+
|
| 314 |
+
# Compute metrics
|
| 315 |
+
tokens_generated = int(tokens_generated or 0)
|
| 316 |
+
reported_tps = (
|
| 317 |
+
response.get("generation_tps", 0.0)
|
| 318 |
+
if isinstance(response, dict)
|
| 319 |
+
else getattr(response, "generation_tps", 0.0)
|
| 320 |
+
)
|
| 321 |
+
tps = float(reported_tps) if reported_tps else (
|
| 322 |
+
tokens_generated / elapsed if elapsed > 0 else 0
|
| 323 |
+
)
|
| 324 |
+
repetition = detect_repetition_rate(text)
|
| 325 |
+
memory_growth = max(0.0, mem_after - mem_before)
|
| 326 |
+
finish_reason = (
|
| 327 |
+
response.get("finish_reason")
|
| 328 |
+
if isinstance(response, dict)
|
| 329 |
+
else getattr(response, "finish_reason", None)
|
| 330 |
+
)
|
| 331 |
+
if finish_reason is not None:
|
| 332 |
+
completed = finish_reason != "length"
|
| 333 |
+
elif tokens_reliable:
|
| 334 |
+
completed = tokens_generated < max_tokens
|
| 335 |
+
else:
|
| 336 |
+
completed = None
|
| 337 |
+
|
| 338 |
+
result = {
|
| 339 |
+
"max_tokens": max_tokens,
|
| 340 |
+
"tokens_generated": tokens_generated,
|
| 341 |
+
"tokens_generated_source": token_count_source,
|
| 342 |
+
"tokens_generated_reliable": tokens_reliable,
|
| 343 |
+
"elapsed_seconds": elapsed,
|
| 344 |
+
"mean_tps": tps,
|
| 345 |
+
"tps_source": (
|
| 346 |
+
"mlx-vlm generation_tps" if reported_tps else "end-to-end estimate"
|
| 347 |
+
),
|
| 348 |
+
"peak_memory_mb": mem_after,
|
| 349 |
+
"memory_growth_mb": memory_growth,
|
| 350 |
+
"repetition_rate": repetition,
|
| 351 |
+
"completed_naturally": completed,
|
| 352 |
+
"finish_reason": finish_reason,
|
| 353 |
+
"forced_minimum_tokens": forced_minimum,
|
| 354 |
+
"status": "success",
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
print(f" Tokens: {tokens_generated} | TPS: {tps:.1f} | "
|
| 358 |
+
f"Memory: {mem_after:.0f} MB (+{memory_growth:.0f}) | "
|
| 359 |
+
f"Repetition: {repetition:.4f}")
|
| 360 |
+
|
| 361 |
+
except Exception as e:
|
| 362 |
+
elapsed = time.perf_counter() - start
|
| 363 |
+
result = {
|
| 364 |
+
"max_tokens": max_tokens,
|
| 365 |
+
"status": "error",
|
| 366 |
+
"error": str(e),
|
| 367 |
+
"elapsed_seconds": elapsed,
|
| 368 |
+
}
|
| 369 |
+
print(f" ERROR: {e}")
|
| 370 |
+
|
| 371 |
+
results.append(result)
|
| 372 |
+
|
| 373 |
+
# Analyze cache behavior
|
| 374 |
+
successful = [r for r in results if r["status"] == "success"]
|
| 375 |
+
reliable_successful = [
|
| 376 |
+
r for r in successful if r.get("tokens_generated_reliable", True)
|
| 377 |
+
]
|
| 378 |
+
cache_bounded, cache_reason = analyze_cache_behavior(results)
|
| 379 |
+
|
| 380 |
+
# TPS degradation check
|
| 381 |
+
tps_stable: bool | None = None
|
| 382 |
+
distinct_tps_runs = sorted(
|
| 383 |
+
reliable_successful,
|
| 384 |
+
key=lambda result: result.get("tokens_generated", 0),
|
| 385 |
+
)
|
| 386 |
+
if (
|
| 387 |
+
len({r.get("tokens_generated", 0) for r in distinct_tps_runs}) >= 2
|
| 388 |
+
and distinct_tps_runs[-1].get("tokens_generated", 0)
|
| 389 |
+
>= distinct_tps_runs[0].get("tokens_generated", 0) * 2
|
| 390 |
+
):
|
| 391 |
+
first_tps = distinct_tps_runs[0].get("mean_tps", 0)
|
| 392 |
+
last_tps = distinct_tps_runs[-1].get("mean_tps", 0)
|
| 393 |
+
if first_tps > 0:
|
| 394 |
+
tps_stable = last_tps >= first_tps * 0.7 # Within 30% degradation
|
| 395 |
+
|
| 396 |
+
test_8k = next((r for r in results if r.get("max_tokens") == 8192), None)
|
| 397 |
+
test_8k_passed = bool(
|
| 398 |
+
test_8k
|
| 399 |
+
and test_8k.get("status") == "success"
|
| 400 |
+
and test_8k.get("tokens_generated_reliable", True)
|
| 401 |
+
and test_8k.get("tokens_generated", 0) >= int(8192 * 0.95)
|
| 402 |
+
)
|
| 403 |
+
|
| 404 |
+
summary = {
|
| 405 |
+
"model_path": Path(model_path).name if Path(model_path).is_dir() else model_path,
|
| 406 |
+
"image_path": Path(image_path).name,
|
| 407 |
+
"prompt": prompt,
|
| 408 |
+
"force_min_tokens": force_min_tokens,
|
| 409 |
+
"forced_eos_token_ids": eos_token_ids,
|
| 410 |
+
"platform": platform.platform(),
|
| 411 |
+
"processor": platform.processor(),
|
| 412 |
+
"baseline_memory_mb": baseline_memory,
|
| 413 |
+
"test_results": results,
|
| 414 |
+
"analysis": {
|
| 415 |
+
"cache_appears_bounded": cache_bounded,
|
| 416 |
+
"cache_analysis_reason": cache_reason,
|
| 417 |
+
"tps_stable_over_length": tps_stable,
|
| 418 |
+
"max_successful_tokens": max(
|
| 419 |
+
(r["tokens_generated"] for r in reliable_successful), default=0
|
| 420 |
+
),
|
| 421 |
+
"max_repetition_rate": max(
|
| 422 |
+
(r.get("repetition_rate", 0) for r in successful), default=0
|
| 423 |
+
),
|
| 424 |
+
},
|
| 425 |
+
"pass_conditions": {
|
| 426 |
+
"cache_bounded": cache_bounded is True,
|
| 427 |
+
"tps_stable": tps_stable is True,
|
| 428 |
+
"8k_test_passed": test_8k_passed,
|
| 429 |
+
},
|
| 430 |
+
}
|
| 431 |
+
summary["passed"] = all(summary["pass_conditions"].values())
|
| 432 |
+
|
| 433 |
+
return summary
|
| 434 |
+
|
| 435 |
+
|
| 436 |
+
def main():
|
| 437 |
+
parser = argparse.ArgumentParser(description="R-SWA cache validation")
|
| 438 |
+
parser.add_argument("--model-path", required=True)
|
| 439 |
+
parser.add_argument("--image", required=True, help="Test image (should produce long output)")
|
| 440 |
+
parser.add_argument("--output", type=Path, default=Path("benchmarks/results/rswa_results.json"))
|
| 441 |
+
parser.add_argument("--prompt", default="<image>document parsing.")
|
| 442 |
+
parser.add_argument("--lengths", type=int, nargs="+", default=None,
|
| 443 |
+
help="Custom output lengths to test")
|
| 444 |
+
parser.add_argument(
|
| 445 |
+
"--force-min-tokens",
|
| 446 |
+
type=int,
|
| 447 |
+
default=None,
|
| 448 |
+
help=(
|
| 449 |
+
"Stress-test only: suppress EOS until this many new tokens "
|
| 450 |
+
"(capped at each --lengths value)"
|
| 451 |
+
),
|
| 452 |
+
)
|
| 453 |
+
args = parser.parse_args()
|
| 454 |
+
|
| 455 |
+
print("=" * 60)
|
| 456 |
+
print("R-SWA Cache Validation — Unlimited-OCR MLX")
|
| 457 |
+
print("=" * 60)
|
| 458 |
+
|
| 459 |
+
results = run_rswa_validation(
|
| 460 |
+
model_path=args.model_path,
|
| 461 |
+
image_path=args.image,
|
| 462 |
+
prompt=args.prompt,
|
| 463 |
+
test_lengths=args.lengths,
|
| 464 |
+
force_min_tokens=args.force_min_tokens,
|
| 465 |
+
)
|
| 466 |
+
|
| 467 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 468 |
+
args.output.write_text(
|
| 469 |
+
json.dumps(results, indent=2, ensure_ascii=False),
|
| 470 |
+
encoding="utf-8",
|
| 471 |
+
)
|
| 472 |
+
|
| 473 |
+
# Print summary
|
| 474 |
+
analysis = results["analysis"]
|
| 475 |
+
pass_cond = results["pass_conditions"]
|
| 476 |
+
|
| 477 |
+
print(f"\n{'='*60}")
|
| 478 |
+
print("R-SWA Validation Summary:")
|
| 479 |
+
cache_status = (
|
| 480 |
+
"PASS" if analysis["cache_appears_bounded"] is True
|
| 481 |
+
else "FAIL" if analysis["cache_appears_bounded"] is False
|
| 482 |
+
else "INCONCLUSIVE"
|
| 483 |
+
)
|
| 484 |
+
tps_status = (
|
| 485 |
+
"PASS" if analysis["tps_stable_over_length"] is True
|
| 486 |
+
else "FAIL" if analysis["tps_stable_over_length"] is False
|
| 487 |
+
else "INCONCLUSIVE"
|
| 488 |
+
)
|
| 489 |
+
print(f" Cache bounded: {cache_status}")
|
| 490 |
+
print(f" TPS stable: {tps_status}")
|
| 491 |
+
print(f" 8K test: {'PASS' if pass_cond['8k_test_passed'] else 'FAIL'}")
|
| 492 |
+
print(f" Max tokens: {analysis['max_successful_tokens']}")
|
| 493 |
+
print(f" Max repetition: {analysis['max_repetition_rate']:.4f}")
|
| 494 |
+
print(f"\nSaved to: {args.output}")
|
| 495 |
+
if not results["passed"]:
|
| 496 |
+
raise SystemExit(1)
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
if __name__ == "__main__":
|
| 500 |
+
main()
|
benchmarks/run_accuracy.py
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Run accuracy benchmarks on evaluation images.
|
| 2 |
+
|
| 3 |
+
Usage:
|
| 4 |
+
python benchmarks/run_accuracy.py \
|
| 5 |
+
--model-path AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8 \
|
| 6 |
+
--eval-dir ./benchmarks/eval_data/ \
|
| 7 |
+
--output benchmarks/results/accuracy_results.json
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import argparse
|
| 13 |
+
from collections import Counter
|
| 14 |
+
import json
|
| 15 |
+
import sys
|
| 16 |
+
import time
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
|
| 19 |
+
# Add the project and src roots for both package and benchmark imports when the
|
| 20 |
+
# file is executed directly (``python benchmarks/run_accuracy.py``).
|
| 21 |
+
PROJECT_ROOT = Path(__file__).parent.parent
|
| 22 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 23 |
+
sys.path.insert(0, str(PROJECT_ROOT / "src"))
|
| 24 |
+
|
| 25 |
+
from benchmarks.evaluate_cer import (
|
| 26 |
+
compute_cer, compute_wer, compute_digit_cer,
|
| 27 |
+
compute_cjk_cer, detect_repetition, extract_cjk_characters,
|
| 28 |
+
)
|
| 29 |
+
from benchmarks.normalize_output import normalize_ocr_output
|
| 30 |
+
from benchmarks.evaluate_tables import evaluate_tables
|
| 31 |
+
from unlimited_ocr.engine import SlidingWindowNoRepeatNGramProcessor
|
| 32 |
+
from unlimited_ocr.profiles import get_profile
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def run_accuracy_benchmark(
|
| 36 |
+
model_path: str,
|
| 37 |
+
eval_dir: Path,
|
| 38 |
+
prompt: str = "<image>document parsing.",
|
| 39 |
+
max_tokens: int | None = None,
|
| 40 |
+
profile: str = "accurate",
|
| 41 |
+
) -> dict:
|
| 42 |
+
"""Run accuracy benchmark on evaluation dataset.
|
| 43 |
+
|
| 44 |
+
Expects eval_dir structure:
|
| 45 |
+
eval_dir/
|
| 46 |
+
├── images/ # Input images
|
| 47 |
+
└── ground_truth/ # Reference .txt files (same stem)
|
| 48 |
+
"""
|
| 49 |
+
images_dir = eval_dir / "images"
|
| 50 |
+
gt_dir = eval_dir / "ground_truth"
|
| 51 |
+
|
| 52 |
+
if not images_dir.is_dir():
|
| 53 |
+
raise FileNotFoundError(f"Images directory not found: {images_dir}")
|
| 54 |
+
if not gt_dir.is_dir():
|
| 55 |
+
raise FileNotFoundError(f"Ground truth directory not found: {gt_dir}")
|
| 56 |
+
|
| 57 |
+
if not isinstance(prompt, str) or prompt.count("<image>") != 1:
|
| 58 |
+
raise ValueError("prompt must contain exactly one literal '<image>' token")
|
| 59 |
+
generation_profile = get_profile(profile)
|
| 60 |
+
token_limit = generation_profile.max_tokens if max_tokens is None else max_tokens
|
| 61 |
+
if not isinstance(token_limit, int) or isinstance(token_limit, bool) or token_limit < 1:
|
| 62 |
+
raise ValueError("max_tokens must be a positive integer")
|
| 63 |
+
|
| 64 |
+
# Collect image files
|
| 65 |
+
image_files = sorted(
|
| 66 |
+
f for f in images_dir.iterdir()
|
| 67 |
+
if f.is_file() and f.suffix.lower() in {
|
| 68 |
+
".jpg", ".jpeg", ".png", ".tiff", ".tif", ".webp", ".bmp"
|
| 69 |
+
}
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
print(f"Found {len(image_files)} images")
|
| 73 |
+
if not image_files:
|
| 74 |
+
raise ValueError(f"No supported images found in: {images_dir}")
|
| 75 |
+
|
| 76 |
+
duplicate_stems = sorted(
|
| 77 |
+
stem for stem, count in Counter(path.stem for path in image_files).items()
|
| 78 |
+
if count > 1
|
| 79 |
+
)
|
| 80 |
+
if duplicate_stems:
|
| 81 |
+
raise ValueError(
|
| 82 |
+
"Multiple input images share the same stem: "
|
| 83 |
+
+ ", ".join(duplicate_stems)
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
missing_ground_truth = [
|
| 87 |
+
image_file.name
|
| 88 |
+
for image_file in image_files
|
| 89 |
+
if not (gt_dir / f"{image_file.stem}.txt").is_file()
|
| 90 |
+
]
|
| 91 |
+
if missing_ground_truth:
|
| 92 |
+
raise FileNotFoundError(
|
| 93 |
+
"Missing ground-truth text for: " + ", ".join(missing_ground_truth)
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
evaluation_files = [
|
| 97 |
+
(image_file, gt_dir / f"{image_file.stem}.txt")
|
| 98 |
+
for image_file in image_files
|
| 99 |
+
]
|
| 100 |
+
|
| 101 |
+
# Import and load only after validating the dataset and generation options.
|
| 102 |
+
from mlx_vlm import load, generate
|
| 103 |
+
|
| 104 |
+
print(f"Loading model: {model_path}")
|
| 105 |
+
model, processor = load(model_path)
|
| 106 |
+
|
| 107 |
+
results = []
|
| 108 |
+
total_time = 0.0
|
| 109 |
+
|
| 110 |
+
for i, (img_file, gt_file) in enumerate(evaluation_files):
|
| 111 |
+
reference = normalize_ocr_output(gt_file.read_text(encoding="utf-8"))
|
| 112 |
+
|
| 113 |
+
# Run inference
|
| 114 |
+
start = time.perf_counter()
|
| 115 |
+
response = generate(
|
| 116 |
+
model, processor,
|
| 117 |
+
prompt=prompt,
|
| 118 |
+
image=[str(img_file)],
|
| 119 |
+
max_tokens=token_limit,
|
| 120 |
+
temperature=generation_profile.temperature,
|
| 121 |
+
top_p=generation_profile.top_p,
|
| 122 |
+
repetition_penalty=generation_profile.repetition_penalty,
|
| 123 |
+
logits_processors=[SlidingWindowNoRepeatNGramProcessor(
|
| 124 |
+
generation_profile.no_repeat_ngram_size,
|
| 125 |
+
128,
|
| 126 |
+
)],
|
| 127 |
+
verbose=False,
|
| 128 |
+
)
|
| 129 |
+
elapsed = time.perf_counter() - start
|
| 130 |
+
total_time += elapsed
|
| 131 |
+
|
| 132 |
+
# Extract text
|
| 133 |
+
if isinstance(response, str):
|
| 134 |
+
hypothesis = response
|
| 135 |
+
elif hasattr(response, "text"):
|
| 136 |
+
hypothesis = response.text
|
| 137 |
+
else:
|
| 138 |
+
hypothesis = str(response)
|
| 139 |
+
hypothesis = normalize_ocr_output(hypothesis)
|
| 140 |
+
|
| 141 |
+
# Compute metrics
|
| 142 |
+
cer = compute_cer(reference, hypothesis)
|
| 143 |
+
wer = compute_wer(reference, hypothesis)
|
| 144 |
+
digit_cer = compute_digit_cer(reference, hypothesis)
|
| 145 |
+
cjk_cer = compute_cjk_cer(reference, hypothesis)
|
| 146 |
+
repetition = detect_repetition(hypothesis)
|
| 147 |
+
table_result = evaluate_tables(reference, hypothesis)
|
| 148 |
+
table_score = (
|
| 149 |
+
table_result["mean_score"]
|
| 150 |
+
if table_result["num_ref_tables"] > 0
|
| 151 |
+
else None
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
result = {
|
| 155 |
+
"file": img_file.name,
|
| 156 |
+
"cer": cer,
|
| 157 |
+
"wer": wer,
|
| 158 |
+
"digit_cer": digit_cer,
|
| 159 |
+
"cjk_cer": cjk_cer,
|
| 160 |
+
"repetition_rate": repetition,
|
| 161 |
+
"table_score": table_score,
|
| 162 |
+
"elapsed_seconds": elapsed,
|
| 163 |
+
"ref_length": len(reference),
|
| 164 |
+
"hyp_length": len(hypothesis),
|
| 165 |
+
"ref_digit_count": sum(
|
| 166 |
+
char.isascii() and char.isdigit() for char in reference
|
| 167 |
+
),
|
| 168 |
+
"ref_cjk_count": len(extract_cjk_characters(reference)),
|
| 169 |
+
}
|
| 170 |
+
results.append(result)
|
| 171 |
+
|
| 172 |
+
print(f" [{i+1}/{len(evaluation_files)}] {img_file.name}: CER={cer:.4f} "
|
| 173 |
+
f"DigitCER={digit_cer:.4f} ({elapsed:.1f}s)")
|
| 174 |
+
|
| 175 |
+
# Aggregate
|
| 176 |
+
import numpy as np
|
| 177 |
+
digit_scores = [r["digit_cer"] for r in results if r["ref_digit_count"] > 0]
|
| 178 |
+
cjk_scores = [r["cjk_cer"] for r in results if r["ref_cjk_count"] > 0]
|
| 179 |
+
table_scores = [r["table_score"] for r in results if r["table_score"] is not None]
|
| 180 |
+
summary = {
|
| 181 |
+
"model_path": Path(model_path).name if Path(model_path).is_dir() else model_path,
|
| 182 |
+
"prompt": prompt,
|
| 183 |
+
"max_tokens": token_limit,
|
| 184 |
+
"profile": profile,
|
| 185 |
+
"generation_settings": {
|
| 186 |
+
"temperature": generation_profile.temperature,
|
| 187 |
+
"top_p": generation_profile.top_p,
|
| 188 |
+
"repetition_penalty": generation_profile.repetition_penalty,
|
| 189 |
+
"no_repeat_ngram_size": generation_profile.no_repeat_ngram_size,
|
| 190 |
+
"ngram_window": 128,
|
| 191 |
+
},
|
| 192 |
+
"num_images": len(image_files),
|
| 193 |
+
"num_samples": len(results),
|
| 194 |
+
"total_time_seconds": total_time,
|
| 195 |
+
"mean_cer": float(np.mean([r["cer"] for r in results])) if results else 0,
|
| 196 |
+
"mean_wer": float(np.mean([r["wer"] for r in results])) if results else 0,
|
| 197 |
+
"num_digit_samples": len(digit_scores),
|
| 198 |
+
"num_cjk_samples": len(cjk_scores),
|
| 199 |
+
"mean_digit_cer": float(np.mean(digit_scores)) if digit_scores else None,
|
| 200 |
+
"mean_cjk_cer": float(np.mean(cjk_scores)) if cjk_scores else None,
|
| 201 |
+
"num_table_samples": len(table_scores),
|
| 202 |
+
"mean_table_score": float(np.mean(table_scores)) if table_scores else None,
|
| 203 |
+
"mean_repetition_rate": float(np.mean([r["repetition_rate"] for r in results])) if results else 0,
|
| 204 |
+
"per_file": results,
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
return summary
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def main():
|
| 211 |
+
parser = argparse.ArgumentParser(description="Run OCR accuracy benchmark")
|
| 212 |
+
parser.add_argument("--model-path", required=True)
|
| 213 |
+
parser.add_argument("--eval-dir", required=True, type=Path)
|
| 214 |
+
parser.add_argument("--output", type=Path, default=Path("benchmarks/results/accuracy_results.json"))
|
| 215 |
+
parser.add_argument("--prompt", default="<image>document parsing.")
|
| 216 |
+
parser.add_argument("--max-tokens", type=int, default=None,
|
| 217 |
+
help="Override the selected profile's token limit")
|
| 218 |
+
parser.add_argument("--profile", default="accurate",
|
| 219 |
+
choices=["accurate", "fast", "long-document", "plain-text", "markdown"])
|
| 220 |
+
args = parser.parse_args()
|
| 221 |
+
|
| 222 |
+
print("=" * 60)
|
| 223 |
+
print("Unlimited-OCR Accuracy Benchmark")
|
| 224 |
+
print("=" * 60)
|
| 225 |
+
|
| 226 |
+
results = run_accuracy_benchmark(
|
| 227 |
+
model_path=args.model_path,
|
| 228 |
+
eval_dir=args.eval_dir,
|
| 229 |
+
prompt=args.prompt,
|
| 230 |
+
max_tokens=args.max_tokens,
|
| 231 |
+
profile=args.profile,
|
| 232 |
+
)
|
| 233 |
+
|
| 234 |
+
# Save results
|
| 235 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 236 |
+
args.output.write_text(json.dumps(results, indent=2, ensure_ascii=False), encoding="utf-8")
|
| 237 |
+
|
| 238 |
+
def metric(value):
|
| 239 |
+
return "N/A" if value is None else f"{value:.4f}"
|
| 240 |
+
|
| 241 |
+
print(f"\n{'='*60}")
|
| 242 |
+
print(f"Results: CER={results['mean_cer']:.4f} | "
|
| 243 |
+
f"DigitCER={metric(results['mean_digit_cer'])} | "
|
| 244 |
+
f"CJK={metric(results['mean_cjk_cer'])}")
|
| 245 |
+
print(f"Saved to: {args.output}")
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
if __name__ == "__main__":
|
| 249 |
+
main()
|
benchmarks/run_performance.py
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Run performance benchmarks (decode TPS, end-to-end latency, and memory).
|
| 2 |
+
|
| 3 |
+
Usage:
|
| 4 |
+
python benchmarks/run_performance.py \
|
| 5 |
+
--model-path AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8 \
|
| 6 |
+
--image test_image.jpg \
|
| 7 |
+
--output benchmarks/results/performance_results.json
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import argparse
|
| 13 |
+
import importlib.metadata
|
| 14 |
+
import json
|
| 15 |
+
import os
|
| 16 |
+
import platform
|
| 17 |
+
import sys
|
| 18 |
+
import time
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
|
| 21 |
+
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def get_memory_usage_mb() -> float:
|
| 25 |
+
"""Get the process peak resident-set size in MB."""
|
| 26 |
+
try:
|
| 27 |
+
import resource
|
| 28 |
+
usage = resource.getrusage(resource.RUSAGE_SELF)
|
| 29 |
+
# macOS reports bytes; Linux and most BSD-derived Python builds report
|
| 30 |
+
# KiB. The benchmark is Apple-focused, but avoid nonsensical values
|
| 31 |
+
# when its reporting helpers are run elsewhere.
|
| 32 |
+
divisor = 1024 * 1024 if sys.platform == "darwin" else 1024
|
| 33 |
+
return usage.ru_maxrss / divisor
|
| 34 |
+
except Exception:
|
| 35 |
+
return 0.0
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def get_system_info() -> dict:
|
| 39 |
+
"""Collect system information for reproducibility."""
|
| 40 |
+
info = {
|
| 41 |
+
"platform": platform.platform(),
|
| 42 |
+
"processor": platform.processor(),
|
| 43 |
+
"python_version": platform.python_version(),
|
| 44 |
+
"machine": platform.machine(),
|
| 45 |
+
}
|
| 46 |
+
for distribution, key in (("mlx", "mlx_version"), ("mlx-vlm", "mlx_vlm_version")):
|
| 47 |
+
try:
|
| 48 |
+
info[key] = importlib.metadata.version(distribution)
|
| 49 |
+
except importlib.metadata.PackageNotFoundError:
|
| 50 |
+
info[key] = None
|
| 51 |
+
|
| 52 |
+
# Try to get chip info on macOS
|
| 53 |
+
try:
|
| 54 |
+
import subprocess
|
| 55 |
+
result = subprocess.run(
|
| 56 |
+
["sysctl", "-n", "machdep.cpu.brand_string"],
|
| 57 |
+
capture_output=True, text=True, timeout=5
|
| 58 |
+
)
|
| 59 |
+
if result.returncode == 0:
|
| 60 |
+
info["chip"] = result.stdout.strip()
|
| 61 |
+
except Exception:
|
| 62 |
+
pass
|
| 63 |
+
|
| 64 |
+
# Try to get memory info
|
| 65 |
+
try:
|
| 66 |
+
import subprocess
|
| 67 |
+
result = subprocess.run(
|
| 68 |
+
["sysctl", "-n", "hw.memsize"],
|
| 69 |
+
capture_output=True, text=True, timeout=5
|
| 70 |
+
)
|
| 71 |
+
if result.returncode == 0:
|
| 72 |
+
info["total_memory_gb"] = int(result.stdout.strip()) / (1024**3)
|
| 73 |
+
except Exception:
|
| 74 |
+
pass
|
| 75 |
+
|
| 76 |
+
return info
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def run_performance_benchmark(
|
| 80 |
+
model_path: str,
|
| 81 |
+
image_path: str,
|
| 82 |
+
prompt: str = "<image>document parsing.",
|
| 83 |
+
max_tokens: int = 4096,
|
| 84 |
+
num_warmup: int = 1,
|
| 85 |
+
num_runs: int = 3,
|
| 86 |
+
) -> dict:
|
| 87 |
+
"""Run performance benchmark measuring TPS, memory, and latency."""
|
| 88 |
+
if not Path(image_path).is_file():
|
| 89 |
+
raise FileNotFoundError(f"Test image not found: {image_path}")
|
| 90 |
+
if not isinstance(prompt, str) or prompt.count("<image>") != 1:
|
| 91 |
+
raise ValueError("prompt must contain exactly one literal '<image>' token")
|
| 92 |
+
if not isinstance(max_tokens, int) or isinstance(max_tokens, bool) or max_tokens < 1:
|
| 93 |
+
raise ValueError("max_tokens must be a positive integer")
|
| 94 |
+
if not isinstance(num_warmup, int) or isinstance(num_warmup, bool) or num_warmup < 0:
|
| 95 |
+
raise ValueError("num_warmup must be a non-negative integer")
|
| 96 |
+
if not isinstance(num_runs, int) or isinstance(num_runs, bool) or num_runs < 1:
|
| 97 |
+
raise ValueError("num_runs must be a positive integer")
|
| 98 |
+
|
| 99 |
+
from mlx_vlm import load, generate
|
| 100 |
+
|
| 101 |
+
system_info = get_system_info()
|
| 102 |
+
total_memory = system_info.get("total_memory_gb")
|
| 103 |
+
memory_label = f"{total_memory:.0f} GB" if isinstance(total_memory, (int, float)) else "Unknown memory"
|
| 104 |
+
print(f"System: {system_info.get('chip', 'Unknown')} | {memory_label}")
|
| 105 |
+
|
| 106 |
+
# Measure model load time
|
| 107 |
+
load_start = time.perf_counter()
|
| 108 |
+
model, processor = load(model_path)
|
| 109 |
+
load_time = time.perf_counter() - load_start
|
| 110 |
+
mem_after_load = get_memory_usage_mb()
|
| 111 |
+
|
| 112 |
+
print(f"Model load time: {load_time:.2f}s")
|
| 113 |
+
print(f"Memory after load: {mem_after_load:.0f} MB")
|
| 114 |
+
|
| 115 |
+
# Warmup runs
|
| 116 |
+
print(f"\nWarmup ({num_warmup} run(s))...")
|
| 117 |
+
for _ in range(num_warmup):
|
| 118 |
+
generate(model, processor, prompt=prompt, image=[image_path],
|
| 119 |
+
max_tokens=128, temperature=0.0, verbose=False)
|
| 120 |
+
|
| 121 |
+
import mlx.core as mx
|
| 122 |
+
mx.clear_cache()
|
| 123 |
+
|
| 124 |
+
# Benchmark runs
|
| 125 |
+
print(f"Benchmarking ({num_runs} runs, max_tokens={max_tokens})...")
|
| 126 |
+
runs = []
|
| 127 |
+
|
| 128 |
+
for i in range(num_runs):
|
| 129 |
+
active_memory_before_mb = mx.get_active_memory() / 1e6
|
| 130 |
+
mx.reset_peak_memory()
|
| 131 |
+
start = time.perf_counter()
|
| 132 |
+
|
| 133 |
+
response = generate(
|
| 134 |
+
model, processor,
|
| 135 |
+
prompt=prompt,
|
| 136 |
+
image=[image_path],
|
| 137 |
+
max_tokens=max_tokens,
|
| 138 |
+
temperature=0.0,
|
| 139 |
+
verbose=False,
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
elapsed = time.perf_counter() - start
|
| 143 |
+
process_peak_mb = get_memory_usage_mb()
|
| 144 |
+
|
| 145 |
+
# Extract token count
|
| 146 |
+
if isinstance(response, str):
|
| 147 |
+
text = response
|
| 148 |
+
tokens = len(text.split())
|
| 149 |
+
token_count_source = "whitespace estimate"
|
| 150 |
+
elif hasattr(response, "text"):
|
| 151 |
+
text = response.text
|
| 152 |
+
if not isinstance(text, str):
|
| 153 |
+
raise TypeError("mlx-vlm returned a non-string text field")
|
| 154 |
+
raw_tokens = getattr(
|
| 155 |
+
response,
|
| 156 |
+
"generation_tokens",
|
| 157 |
+
getattr(response, "tokens_generated", None),
|
| 158 |
+
)
|
| 159 |
+
if raw_tokens is None:
|
| 160 |
+
tokens = len(text.split())
|
| 161 |
+
token_count_source = "whitespace estimate"
|
| 162 |
+
else:
|
| 163 |
+
tokens = raw_tokens
|
| 164 |
+
token_count_source = "mlx-vlm token count"
|
| 165 |
+
elif isinstance(response, dict):
|
| 166 |
+
text = response.get("text", "")
|
| 167 |
+
if not isinstance(text, str):
|
| 168 |
+
raise TypeError("mlx-vlm returned a non-string text field")
|
| 169 |
+
raw_tokens = response.get(
|
| 170 |
+
"generation_tokens", response.get("tokens_generated")
|
| 171 |
+
)
|
| 172 |
+
if raw_tokens is None:
|
| 173 |
+
tokens = len(text.split())
|
| 174 |
+
token_count_source = "whitespace estimate"
|
| 175 |
+
else:
|
| 176 |
+
tokens = raw_tokens
|
| 177 |
+
token_count_source = "mlx-vlm token count"
|
| 178 |
+
else:
|
| 179 |
+
text = str(response)
|
| 180 |
+
tokens = len(text.split())
|
| 181 |
+
token_count_source = "whitespace estimate"
|
| 182 |
+
tokens = int(tokens or 0)
|
| 183 |
+
|
| 184 |
+
# Prefer mlx-vlm's decode-only rate; the fallback is end-to-end and is
|
| 185 |
+
# explicitly identified in each run.
|
| 186 |
+
reported_tps = getattr(response, "generation_tps", 0.0)
|
| 187 |
+
if reported_tps:
|
| 188 |
+
tps = float(reported_tps)
|
| 189 |
+
tps_source = "mlx-vlm generation_tps"
|
| 190 |
+
else:
|
| 191 |
+
tps = tokens / elapsed if elapsed > 0 and tokens > 0 else 0
|
| 192 |
+
tps_source = "end-to-end estimate"
|
| 193 |
+
|
| 194 |
+
reported_peak_gb = float(getattr(response, "peak_memory", 0.0) or 0.0)
|
| 195 |
+
mlx_peak_mb = (
|
| 196 |
+
reported_peak_gb * 1000
|
| 197 |
+
if reported_peak_gb > 0
|
| 198 |
+
else mx.get_peak_memory() / 1e6
|
| 199 |
+
)
|
| 200 |
+
run_result = {
|
| 201 |
+
"run": i + 1,
|
| 202 |
+
"elapsed_seconds": elapsed,
|
| 203 |
+
"tokens_generated": tokens,
|
| 204 |
+
"tokens_generated_source": token_count_source,
|
| 205 |
+
"tokens_per_second": tps,
|
| 206 |
+
"tokens_per_second_source": tps_source,
|
| 207 |
+
"peak_memory_mb": mlx_peak_mb,
|
| 208 |
+
"memory_delta_mb": max(0.0, mlx_peak_mb - active_memory_before_mb),
|
| 209 |
+
"process_peak_rss_mb": process_peak_mb,
|
| 210 |
+
"prompt_tokens": int(getattr(response, "prompt_tokens", 0) or 0),
|
| 211 |
+
"prompt_tokens_per_second": float(
|
| 212 |
+
getattr(response, "prompt_tps", 0.0) or 0.0
|
| 213 |
+
),
|
| 214 |
+
"finish_reason": getattr(response, "finish_reason", None),
|
| 215 |
+
}
|
| 216 |
+
runs.append(run_result)
|
| 217 |
+
print(f" Run {i+1}: {elapsed:.2f}s | {tokens} tokens | {tps:.1f} tok/s | "
|
| 218 |
+
f"{mlx_peak_mb:.0f} MB")
|
| 219 |
+
|
| 220 |
+
# Aggregate
|
| 221 |
+
import numpy as np
|
| 222 |
+
tps_values = [r["tokens_per_second"] for r in runs]
|
| 223 |
+
elapsed_values = [r["elapsed_seconds"] for r in runs]
|
| 224 |
+
mem_values = [r["peak_memory_mb"] for r in runs]
|
| 225 |
+
|
| 226 |
+
results = {
|
| 227 |
+
"model_path": Path(model_path).name if Path(model_path).is_dir() else model_path,
|
| 228 |
+
"image_path": Path(image_path).name,
|
| 229 |
+
"prompt": prompt,
|
| 230 |
+
"max_tokens": max_tokens,
|
| 231 |
+
"system": system_info,
|
| 232 |
+
"model_load_time_seconds": load_time,
|
| 233 |
+
"memory_after_load_mb": mem_after_load,
|
| 234 |
+
"num_warmup": num_warmup,
|
| 235 |
+
"num_runs": num_runs,
|
| 236 |
+
"mean_tps": float(np.mean(tps_values)) if tps_values else 0,
|
| 237 |
+
"std_tps": float(np.std(tps_values)) if tps_values else 0,
|
| 238 |
+
"mean_elapsed_seconds": float(np.mean(elapsed_values)),
|
| 239 |
+
"mean_peak_memory_mb": float(np.mean(mem_values)),
|
| 240 |
+
"runs": runs,
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
return results
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def main():
|
| 247 |
+
parser = argparse.ArgumentParser(description="Run OCR performance benchmark")
|
| 248 |
+
parser.add_argument("--model-path", required=True)
|
| 249 |
+
parser.add_argument("--image", required=True, help="Test image path")
|
| 250 |
+
parser.add_argument("--output", type=Path, default=Path("benchmarks/results/performance_results.json"))
|
| 251 |
+
parser.add_argument("--prompt", default="<image>document parsing.")
|
| 252 |
+
parser.add_argument("--max-tokens", type=int, default=4096)
|
| 253 |
+
parser.add_argument("--warmup", type=int, default=1)
|
| 254 |
+
parser.add_argument("--runs", type=int, default=3)
|
| 255 |
+
args = parser.parse_args()
|
| 256 |
+
|
| 257 |
+
print("=" * 60)
|
| 258 |
+
print("Unlimited-OCR Performance Benchmark")
|
| 259 |
+
print("=" * 60)
|
| 260 |
+
|
| 261 |
+
results = run_performance_benchmark(
|
| 262 |
+
model_path=args.model_path,
|
| 263 |
+
image_path=args.image,
|
| 264 |
+
prompt=args.prompt,
|
| 265 |
+
max_tokens=args.max_tokens,
|
| 266 |
+
num_warmup=args.warmup,
|
| 267 |
+
num_runs=args.runs,
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 271 |
+
args.output.write_text(json.dumps(results, indent=2, ensure_ascii=False), encoding="utf-8")
|
| 272 |
+
|
| 273 |
+
print(f"\n{'='*60}")
|
| 274 |
+
print(f"Mean TPS: {results['mean_tps']:.1f} | "
|
| 275 |
+
f"Peak Memory: {results['mean_peak_memory_mb']:.0f} MB")
|
| 276 |
+
print(f"Saved to: {args.output}")
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
if __name__ == "__main__":
|
| 280 |
+
main()
|
chat_template.jinja
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{% for message in messages %}{% if message['role'] == 'user' %}{% elif message['role'] == 'assistant' %}{% endif %}{{message['content']}} {% endfor %}{% if add_generation_prompt %}{% endif %}
|
config.json
ADDED
|
@@ -0,0 +1,1321 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"UnlimitedOCRForCausalLM"
|
| 4 |
+
],
|
| 5 |
+
"bos_token_id": 0,
|
| 6 |
+
"candidate_resolutions": [
|
| 7 |
+
[
|
| 8 |
+
1024,
|
| 9 |
+
1024
|
| 10 |
+
]
|
| 11 |
+
],
|
| 12 |
+
"eos_token_id": 1,
|
| 13 |
+
"first_k_dense_replace": 1,
|
| 14 |
+
"global_view_pos": "head",
|
| 15 |
+
"hidden_size": 1280,
|
| 16 |
+
"intermediate_size": 6848,
|
| 17 |
+
"kv_lora_rank": null,
|
| 18 |
+
"language_config": {
|
| 19 |
+
"architectures": [
|
| 20 |
+
"DeepseekOCRForCausalLM"
|
| 21 |
+
],
|
| 22 |
+
"bos_token_id": 0,
|
| 23 |
+
"eos_token_id": 1,
|
| 24 |
+
"first_k_dense_replace": 1,
|
| 25 |
+
"hidden_size": 1280,
|
| 26 |
+
"intermediate_size": 6848,
|
| 27 |
+
"kv_lora_rank": null,
|
| 28 |
+
"lm_head": true,
|
| 29 |
+
"max_position_embeddings": 32768,
|
| 30 |
+
"moe_intermediate_size": 896,
|
| 31 |
+
"n_group": 1,
|
| 32 |
+
"n_routed_experts": 64,
|
| 33 |
+
"n_shared_experts": 2,
|
| 34 |
+
"num_attention_heads": 10,
|
| 35 |
+
"num_experts_per_tok": 6,
|
| 36 |
+
"num_hidden_layers": 12,
|
| 37 |
+
"num_key_value_heads": 10,
|
| 38 |
+
"q_lora_rank": null,
|
| 39 |
+
"qk_nope_head_dim": 0,
|
| 40 |
+
"qk_rope_head_dim": 0,
|
| 41 |
+
"rm_head": false,
|
| 42 |
+
"topk_group": 1,
|
| 43 |
+
"topk_method": "greedy",
|
| 44 |
+
"torch_dtype": "bfloat16",
|
| 45 |
+
"use_mla": false,
|
| 46 |
+
"v_head_dim": 128,
|
| 47 |
+
"vocab_size": 129280,
|
| 48 |
+
"sliding_window_size": 128,
|
| 49 |
+
"sliding_window": 128
|
| 50 |
+
},
|
| 51 |
+
"lm_head": true,
|
| 52 |
+
"max_position_embeddings": 32768,
|
| 53 |
+
"model_type": "unlimited-ocr",
|
| 54 |
+
"moe_intermediate_size": 896,
|
| 55 |
+
"n_group": 1,
|
| 56 |
+
"n_routed_experts": 64,
|
| 57 |
+
"n_shared_experts": 2,
|
| 58 |
+
"num_attention_heads": 10,
|
| 59 |
+
"num_experts_per_tok": 6,
|
| 60 |
+
"num_hidden_layers": 12,
|
| 61 |
+
"num_key_value_heads": 10,
|
| 62 |
+
"projector_config": {
|
| 63 |
+
"input_dim": 2048,
|
| 64 |
+
"model_type": "mlp_projector",
|
| 65 |
+
"n_embed": 1280,
|
| 66 |
+
"projector_type": "linear"
|
| 67 |
+
},
|
| 68 |
+
"q_lora_rank": null,
|
| 69 |
+
"qk_nope_head_dim": 0,
|
| 70 |
+
"qk_rope_head_dim": 0,
|
| 71 |
+
"quantization": {
|
| 72 |
+
"group_size": 32,
|
| 73 |
+
"bits": 8,
|
| 74 |
+
"mode": "mxfp8",
|
| 75 |
+
"language_model.model.embed_tokens": {
|
| 76 |
+
"group_size": 32,
|
| 77 |
+
"bits": 8,
|
| 78 |
+
"mode": "mxfp8"
|
| 79 |
+
},
|
| 80 |
+
"language_model.model.layers.0.self_attn.q_proj": {
|
| 81 |
+
"group_size": 32,
|
| 82 |
+
"bits": 8,
|
| 83 |
+
"mode": "mxfp8"
|
| 84 |
+
},
|
| 85 |
+
"language_model.model.layers.0.self_attn.k_proj": {
|
| 86 |
+
"group_size": 32,
|
| 87 |
+
"bits": 8,
|
| 88 |
+
"mode": "mxfp8"
|
| 89 |
+
},
|
| 90 |
+
"language_model.model.layers.0.self_attn.v_proj": {
|
| 91 |
+
"group_size": 32,
|
| 92 |
+
"bits": 8,
|
| 93 |
+
"mode": "mxfp8"
|
| 94 |
+
},
|
| 95 |
+
"language_model.model.layers.0.self_attn.o_proj": {
|
| 96 |
+
"group_size": 32,
|
| 97 |
+
"bits": 8,
|
| 98 |
+
"mode": "mxfp8"
|
| 99 |
+
},
|
| 100 |
+
"language_model.model.layers.0.mlp.gate_proj": {
|
| 101 |
+
"group_size": 32,
|
| 102 |
+
"bits": 8,
|
| 103 |
+
"mode": "mxfp8"
|
| 104 |
+
},
|
| 105 |
+
"language_model.model.layers.0.mlp.up_proj": {
|
| 106 |
+
"group_size": 32,
|
| 107 |
+
"bits": 8,
|
| 108 |
+
"mode": "mxfp8"
|
| 109 |
+
},
|
| 110 |
+
"language_model.model.layers.0.mlp.down_proj": {
|
| 111 |
+
"group_size": 32,
|
| 112 |
+
"bits": 8,
|
| 113 |
+
"mode": "mxfp8"
|
| 114 |
+
},
|
| 115 |
+
"language_model.model.layers.1.self_attn.q_proj": {
|
| 116 |
+
"group_size": 32,
|
| 117 |
+
"bits": 8,
|
| 118 |
+
"mode": "mxfp8"
|
| 119 |
+
},
|
| 120 |
+
"language_model.model.layers.1.self_attn.k_proj": {
|
| 121 |
+
"group_size": 32,
|
| 122 |
+
"bits": 8,
|
| 123 |
+
"mode": "mxfp8"
|
| 124 |
+
},
|
| 125 |
+
"language_model.model.layers.1.self_attn.v_proj": {
|
| 126 |
+
"group_size": 32,
|
| 127 |
+
"bits": 8,
|
| 128 |
+
"mode": "mxfp8"
|
| 129 |
+
},
|
| 130 |
+
"language_model.model.layers.1.self_attn.o_proj": {
|
| 131 |
+
"group_size": 32,
|
| 132 |
+
"bits": 8,
|
| 133 |
+
"mode": "mxfp8"
|
| 134 |
+
},
|
| 135 |
+
"language_model.model.layers.1.mlp.switch_mlp.gate_proj": {
|
| 136 |
+
"group_size": 32,
|
| 137 |
+
"bits": 8,
|
| 138 |
+
"mode": "mxfp8"
|
| 139 |
+
},
|
| 140 |
+
"language_model.model.layers.1.mlp.switch_mlp.up_proj": {
|
| 141 |
+
"group_size": 32,
|
| 142 |
+
"bits": 8,
|
| 143 |
+
"mode": "mxfp8"
|
| 144 |
+
},
|
| 145 |
+
"language_model.model.layers.1.mlp.switch_mlp.down_proj": {
|
| 146 |
+
"group_size": 32,
|
| 147 |
+
"bits": 8,
|
| 148 |
+
"mode": "mxfp8"
|
| 149 |
+
},
|
| 150 |
+
"language_model.model.layers.1.mlp.shared_experts.gate_proj": {
|
| 151 |
+
"group_size": 32,
|
| 152 |
+
"bits": 8,
|
| 153 |
+
"mode": "mxfp8"
|
| 154 |
+
},
|
| 155 |
+
"language_model.model.layers.1.mlp.shared_experts.up_proj": {
|
| 156 |
+
"group_size": 32,
|
| 157 |
+
"bits": 8,
|
| 158 |
+
"mode": "mxfp8"
|
| 159 |
+
},
|
| 160 |
+
"language_model.model.layers.1.mlp.shared_experts.down_proj": {
|
| 161 |
+
"group_size": 32,
|
| 162 |
+
"bits": 8,
|
| 163 |
+
"mode": "mxfp8"
|
| 164 |
+
},
|
| 165 |
+
"language_model.model.layers.2.self_attn.q_proj": {
|
| 166 |
+
"group_size": 32,
|
| 167 |
+
"bits": 8,
|
| 168 |
+
"mode": "mxfp8"
|
| 169 |
+
},
|
| 170 |
+
"language_model.model.layers.2.self_attn.k_proj": {
|
| 171 |
+
"group_size": 32,
|
| 172 |
+
"bits": 8,
|
| 173 |
+
"mode": "mxfp8"
|
| 174 |
+
},
|
| 175 |
+
"language_model.model.layers.2.self_attn.v_proj": {
|
| 176 |
+
"group_size": 32,
|
| 177 |
+
"bits": 8,
|
| 178 |
+
"mode": "mxfp8"
|
| 179 |
+
},
|
| 180 |
+
"language_model.model.layers.2.self_attn.o_proj": {
|
| 181 |
+
"group_size": 32,
|
| 182 |
+
"bits": 8,
|
| 183 |
+
"mode": "mxfp8"
|
| 184 |
+
},
|
| 185 |
+
"language_model.model.layers.2.mlp.switch_mlp.gate_proj": {
|
| 186 |
+
"group_size": 32,
|
| 187 |
+
"bits": 8,
|
| 188 |
+
"mode": "mxfp8"
|
| 189 |
+
},
|
| 190 |
+
"language_model.model.layers.2.mlp.switch_mlp.up_proj": {
|
| 191 |
+
"group_size": 32,
|
| 192 |
+
"bits": 8,
|
| 193 |
+
"mode": "mxfp8"
|
| 194 |
+
},
|
| 195 |
+
"language_model.model.layers.2.mlp.switch_mlp.down_proj": {
|
| 196 |
+
"group_size": 32,
|
| 197 |
+
"bits": 8,
|
| 198 |
+
"mode": "mxfp8"
|
| 199 |
+
},
|
| 200 |
+
"language_model.model.layers.2.mlp.shared_experts.gate_proj": {
|
| 201 |
+
"group_size": 32,
|
| 202 |
+
"bits": 8,
|
| 203 |
+
"mode": "mxfp8"
|
| 204 |
+
},
|
| 205 |
+
"language_model.model.layers.2.mlp.shared_experts.up_proj": {
|
| 206 |
+
"group_size": 32,
|
| 207 |
+
"bits": 8,
|
| 208 |
+
"mode": "mxfp8"
|
| 209 |
+
},
|
| 210 |
+
"language_model.model.layers.2.mlp.shared_experts.down_proj": {
|
| 211 |
+
"group_size": 32,
|
| 212 |
+
"bits": 8,
|
| 213 |
+
"mode": "mxfp8"
|
| 214 |
+
},
|
| 215 |
+
"language_model.model.layers.3.self_attn.q_proj": {
|
| 216 |
+
"group_size": 32,
|
| 217 |
+
"bits": 8,
|
| 218 |
+
"mode": "mxfp8"
|
| 219 |
+
},
|
| 220 |
+
"language_model.model.layers.3.self_attn.k_proj": {
|
| 221 |
+
"group_size": 32,
|
| 222 |
+
"bits": 8,
|
| 223 |
+
"mode": "mxfp8"
|
| 224 |
+
},
|
| 225 |
+
"language_model.model.layers.3.self_attn.v_proj": {
|
| 226 |
+
"group_size": 32,
|
| 227 |
+
"bits": 8,
|
| 228 |
+
"mode": "mxfp8"
|
| 229 |
+
},
|
| 230 |
+
"language_model.model.layers.3.self_attn.o_proj": {
|
| 231 |
+
"group_size": 32,
|
| 232 |
+
"bits": 8,
|
| 233 |
+
"mode": "mxfp8"
|
| 234 |
+
},
|
| 235 |
+
"language_model.model.layers.3.mlp.switch_mlp.gate_proj": {
|
| 236 |
+
"group_size": 32,
|
| 237 |
+
"bits": 8,
|
| 238 |
+
"mode": "mxfp8"
|
| 239 |
+
},
|
| 240 |
+
"language_model.model.layers.3.mlp.switch_mlp.up_proj": {
|
| 241 |
+
"group_size": 32,
|
| 242 |
+
"bits": 8,
|
| 243 |
+
"mode": "mxfp8"
|
| 244 |
+
},
|
| 245 |
+
"language_model.model.layers.3.mlp.switch_mlp.down_proj": {
|
| 246 |
+
"group_size": 32,
|
| 247 |
+
"bits": 8,
|
| 248 |
+
"mode": "mxfp8"
|
| 249 |
+
},
|
| 250 |
+
"language_model.model.layers.3.mlp.shared_experts.gate_proj": {
|
| 251 |
+
"group_size": 32,
|
| 252 |
+
"bits": 8,
|
| 253 |
+
"mode": "mxfp8"
|
| 254 |
+
},
|
| 255 |
+
"language_model.model.layers.3.mlp.shared_experts.up_proj": {
|
| 256 |
+
"group_size": 32,
|
| 257 |
+
"bits": 8,
|
| 258 |
+
"mode": "mxfp8"
|
| 259 |
+
},
|
| 260 |
+
"language_model.model.layers.3.mlp.shared_experts.down_proj": {
|
| 261 |
+
"group_size": 32,
|
| 262 |
+
"bits": 8,
|
| 263 |
+
"mode": "mxfp8"
|
| 264 |
+
},
|
| 265 |
+
"language_model.model.layers.4.self_attn.q_proj": {
|
| 266 |
+
"group_size": 32,
|
| 267 |
+
"bits": 8,
|
| 268 |
+
"mode": "mxfp8"
|
| 269 |
+
},
|
| 270 |
+
"language_model.model.layers.4.self_attn.k_proj": {
|
| 271 |
+
"group_size": 32,
|
| 272 |
+
"bits": 8,
|
| 273 |
+
"mode": "mxfp8"
|
| 274 |
+
},
|
| 275 |
+
"language_model.model.layers.4.self_attn.v_proj": {
|
| 276 |
+
"group_size": 32,
|
| 277 |
+
"bits": 8,
|
| 278 |
+
"mode": "mxfp8"
|
| 279 |
+
},
|
| 280 |
+
"language_model.model.layers.4.self_attn.o_proj": {
|
| 281 |
+
"group_size": 32,
|
| 282 |
+
"bits": 8,
|
| 283 |
+
"mode": "mxfp8"
|
| 284 |
+
},
|
| 285 |
+
"language_model.model.layers.4.mlp.switch_mlp.gate_proj": {
|
| 286 |
+
"group_size": 32,
|
| 287 |
+
"bits": 8,
|
| 288 |
+
"mode": "mxfp8"
|
| 289 |
+
},
|
| 290 |
+
"language_model.model.layers.4.mlp.switch_mlp.up_proj": {
|
| 291 |
+
"group_size": 32,
|
| 292 |
+
"bits": 8,
|
| 293 |
+
"mode": "mxfp8"
|
| 294 |
+
},
|
| 295 |
+
"language_model.model.layers.4.mlp.switch_mlp.down_proj": {
|
| 296 |
+
"group_size": 32,
|
| 297 |
+
"bits": 8,
|
| 298 |
+
"mode": "mxfp8"
|
| 299 |
+
},
|
| 300 |
+
"language_model.model.layers.4.mlp.shared_experts.gate_proj": {
|
| 301 |
+
"group_size": 32,
|
| 302 |
+
"bits": 8,
|
| 303 |
+
"mode": "mxfp8"
|
| 304 |
+
},
|
| 305 |
+
"language_model.model.layers.4.mlp.shared_experts.up_proj": {
|
| 306 |
+
"group_size": 32,
|
| 307 |
+
"bits": 8,
|
| 308 |
+
"mode": "mxfp8"
|
| 309 |
+
},
|
| 310 |
+
"language_model.model.layers.4.mlp.shared_experts.down_proj": {
|
| 311 |
+
"group_size": 32,
|
| 312 |
+
"bits": 8,
|
| 313 |
+
"mode": "mxfp8"
|
| 314 |
+
},
|
| 315 |
+
"language_model.model.layers.5.self_attn.q_proj": {
|
| 316 |
+
"group_size": 32,
|
| 317 |
+
"bits": 8,
|
| 318 |
+
"mode": "mxfp8"
|
| 319 |
+
},
|
| 320 |
+
"language_model.model.layers.5.self_attn.k_proj": {
|
| 321 |
+
"group_size": 32,
|
| 322 |
+
"bits": 8,
|
| 323 |
+
"mode": "mxfp8"
|
| 324 |
+
},
|
| 325 |
+
"language_model.model.layers.5.self_attn.v_proj": {
|
| 326 |
+
"group_size": 32,
|
| 327 |
+
"bits": 8,
|
| 328 |
+
"mode": "mxfp8"
|
| 329 |
+
},
|
| 330 |
+
"language_model.model.layers.5.self_attn.o_proj": {
|
| 331 |
+
"group_size": 32,
|
| 332 |
+
"bits": 8,
|
| 333 |
+
"mode": "mxfp8"
|
| 334 |
+
},
|
| 335 |
+
"language_model.model.layers.5.mlp.switch_mlp.gate_proj": {
|
| 336 |
+
"group_size": 32,
|
| 337 |
+
"bits": 8,
|
| 338 |
+
"mode": "mxfp8"
|
| 339 |
+
},
|
| 340 |
+
"language_model.model.layers.5.mlp.switch_mlp.up_proj": {
|
| 341 |
+
"group_size": 32,
|
| 342 |
+
"bits": 8,
|
| 343 |
+
"mode": "mxfp8"
|
| 344 |
+
},
|
| 345 |
+
"language_model.model.layers.5.mlp.switch_mlp.down_proj": {
|
| 346 |
+
"group_size": 32,
|
| 347 |
+
"bits": 8,
|
| 348 |
+
"mode": "mxfp8"
|
| 349 |
+
},
|
| 350 |
+
"language_model.model.layers.5.mlp.shared_experts.gate_proj": {
|
| 351 |
+
"group_size": 32,
|
| 352 |
+
"bits": 8,
|
| 353 |
+
"mode": "mxfp8"
|
| 354 |
+
},
|
| 355 |
+
"language_model.model.layers.5.mlp.shared_experts.up_proj": {
|
| 356 |
+
"group_size": 32,
|
| 357 |
+
"bits": 8,
|
| 358 |
+
"mode": "mxfp8"
|
| 359 |
+
},
|
| 360 |
+
"language_model.model.layers.5.mlp.shared_experts.down_proj": {
|
| 361 |
+
"group_size": 32,
|
| 362 |
+
"bits": 8,
|
| 363 |
+
"mode": "mxfp8"
|
| 364 |
+
},
|
| 365 |
+
"language_model.model.layers.6.self_attn.q_proj": {
|
| 366 |
+
"group_size": 32,
|
| 367 |
+
"bits": 8,
|
| 368 |
+
"mode": "mxfp8"
|
| 369 |
+
},
|
| 370 |
+
"language_model.model.layers.6.self_attn.k_proj": {
|
| 371 |
+
"group_size": 32,
|
| 372 |
+
"bits": 8,
|
| 373 |
+
"mode": "mxfp8"
|
| 374 |
+
},
|
| 375 |
+
"language_model.model.layers.6.self_attn.v_proj": {
|
| 376 |
+
"group_size": 32,
|
| 377 |
+
"bits": 8,
|
| 378 |
+
"mode": "mxfp8"
|
| 379 |
+
},
|
| 380 |
+
"language_model.model.layers.6.self_attn.o_proj": {
|
| 381 |
+
"group_size": 32,
|
| 382 |
+
"bits": 8,
|
| 383 |
+
"mode": "mxfp8"
|
| 384 |
+
},
|
| 385 |
+
"language_model.model.layers.6.mlp.switch_mlp.gate_proj": {
|
| 386 |
+
"group_size": 32,
|
| 387 |
+
"bits": 8,
|
| 388 |
+
"mode": "mxfp8"
|
| 389 |
+
},
|
| 390 |
+
"language_model.model.layers.6.mlp.switch_mlp.up_proj": {
|
| 391 |
+
"group_size": 32,
|
| 392 |
+
"bits": 8,
|
| 393 |
+
"mode": "mxfp8"
|
| 394 |
+
},
|
| 395 |
+
"language_model.model.layers.6.mlp.switch_mlp.down_proj": {
|
| 396 |
+
"group_size": 32,
|
| 397 |
+
"bits": 8,
|
| 398 |
+
"mode": "mxfp8"
|
| 399 |
+
},
|
| 400 |
+
"language_model.model.layers.6.mlp.shared_experts.gate_proj": {
|
| 401 |
+
"group_size": 32,
|
| 402 |
+
"bits": 8,
|
| 403 |
+
"mode": "mxfp8"
|
| 404 |
+
},
|
| 405 |
+
"language_model.model.layers.6.mlp.shared_experts.up_proj": {
|
| 406 |
+
"group_size": 32,
|
| 407 |
+
"bits": 8,
|
| 408 |
+
"mode": "mxfp8"
|
| 409 |
+
},
|
| 410 |
+
"language_model.model.layers.6.mlp.shared_experts.down_proj": {
|
| 411 |
+
"group_size": 32,
|
| 412 |
+
"bits": 8,
|
| 413 |
+
"mode": "mxfp8"
|
| 414 |
+
},
|
| 415 |
+
"language_model.model.layers.7.self_attn.q_proj": {
|
| 416 |
+
"group_size": 32,
|
| 417 |
+
"bits": 8,
|
| 418 |
+
"mode": "mxfp8"
|
| 419 |
+
},
|
| 420 |
+
"language_model.model.layers.7.self_attn.k_proj": {
|
| 421 |
+
"group_size": 32,
|
| 422 |
+
"bits": 8,
|
| 423 |
+
"mode": "mxfp8"
|
| 424 |
+
},
|
| 425 |
+
"language_model.model.layers.7.self_attn.v_proj": {
|
| 426 |
+
"group_size": 32,
|
| 427 |
+
"bits": 8,
|
| 428 |
+
"mode": "mxfp8"
|
| 429 |
+
},
|
| 430 |
+
"language_model.model.layers.7.self_attn.o_proj": {
|
| 431 |
+
"group_size": 32,
|
| 432 |
+
"bits": 8,
|
| 433 |
+
"mode": "mxfp8"
|
| 434 |
+
},
|
| 435 |
+
"language_model.model.layers.7.mlp.switch_mlp.gate_proj": {
|
| 436 |
+
"group_size": 32,
|
| 437 |
+
"bits": 8,
|
| 438 |
+
"mode": "mxfp8"
|
| 439 |
+
},
|
| 440 |
+
"language_model.model.layers.7.mlp.switch_mlp.up_proj": {
|
| 441 |
+
"group_size": 32,
|
| 442 |
+
"bits": 8,
|
| 443 |
+
"mode": "mxfp8"
|
| 444 |
+
},
|
| 445 |
+
"language_model.model.layers.7.mlp.switch_mlp.down_proj": {
|
| 446 |
+
"group_size": 32,
|
| 447 |
+
"bits": 8,
|
| 448 |
+
"mode": "mxfp8"
|
| 449 |
+
},
|
| 450 |
+
"language_model.model.layers.7.mlp.shared_experts.gate_proj": {
|
| 451 |
+
"group_size": 32,
|
| 452 |
+
"bits": 8,
|
| 453 |
+
"mode": "mxfp8"
|
| 454 |
+
},
|
| 455 |
+
"language_model.model.layers.7.mlp.shared_experts.up_proj": {
|
| 456 |
+
"group_size": 32,
|
| 457 |
+
"bits": 8,
|
| 458 |
+
"mode": "mxfp8"
|
| 459 |
+
},
|
| 460 |
+
"language_model.model.layers.7.mlp.shared_experts.down_proj": {
|
| 461 |
+
"group_size": 32,
|
| 462 |
+
"bits": 8,
|
| 463 |
+
"mode": "mxfp8"
|
| 464 |
+
},
|
| 465 |
+
"language_model.model.layers.8.self_attn.q_proj": {
|
| 466 |
+
"group_size": 32,
|
| 467 |
+
"bits": 8,
|
| 468 |
+
"mode": "mxfp8"
|
| 469 |
+
},
|
| 470 |
+
"language_model.model.layers.8.self_attn.k_proj": {
|
| 471 |
+
"group_size": 32,
|
| 472 |
+
"bits": 8,
|
| 473 |
+
"mode": "mxfp8"
|
| 474 |
+
},
|
| 475 |
+
"language_model.model.layers.8.self_attn.v_proj": {
|
| 476 |
+
"group_size": 32,
|
| 477 |
+
"bits": 8,
|
| 478 |
+
"mode": "mxfp8"
|
| 479 |
+
},
|
| 480 |
+
"language_model.model.layers.8.self_attn.o_proj": {
|
| 481 |
+
"group_size": 32,
|
| 482 |
+
"bits": 8,
|
| 483 |
+
"mode": "mxfp8"
|
| 484 |
+
},
|
| 485 |
+
"language_model.model.layers.8.mlp.switch_mlp.gate_proj": {
|
| 486 |
+
"group_size": 32,
|
| 487 |
+
"bits": 8,
|
| 488 |
+
"mode": "mxfp8"
|
| 489 |
+
},
|
| 490 |
+
"language_model.model.layers.8.mlp.switch_mlp.up_proj": {
|
| 491 |
+
"group_size": 32,
|
| 492 |
+
"bits": 8,
|
| 493 |
+
"mode": "mxfp8"
|
| 494 |
+
},
|
| 495 |
+
"language_model.model.layers.8.mlp.switch_mlp.down_proj": {
|
| 496 |
+
"group_size": 32,
|
| 497 |
+
"bits": 8,
|
| 498 |
+
"mode": "mxfp8"
|
| 499 |
+
},
|
| 500 |
+
"language_model.model.layers.8.mlp.shared_experts.gate_proj": {
|
| 501 |
+
"group_size": 32,
|
| 502 |
+
"bits": 8,
|
| 503 |
+
"mode": "mxfp8"
|
| 504 |
+
},
|
| 505 |
+
"language_model.model.layers.8.mlp.shared_experts.up_proj": {
|
| 506 |
+
"group_size": 32,
|
| 507 |
+
"bits": 8,
|
| 508 |
+
"mode": "mxfp8"
|
| 509 |
+
},
|
| 510 |
+
"language_model.model.layers.8.mlp.shared_experts.down_proj": {
|
| 511 |
+
"group_size": 32,
|
| 512 |
+
"bits": 8,
|
| 513 |
+
"mode": "mxfp8"
|
| 514 |
+
},
|
| 515 |
+
"language_model.model.layers.9.self_attn.q_proj": {
|
| 516 |
+
"group_size": 32,
|
| 517 |
+
"bits": 8,
|
| 518 |
+
"mode": "mxfp8"
|
| 519 |
+
},
|
| 520 |
+
"language_model.model.layers.9.self_attn.k_proj": {
|
| 521 |
+
"group_size": 32,
|
| 522 |
+
"bits": 8,
|
| 523 |
+
"mode": "mxfp8"
|
| 524 |
+
},
|
| 525 |
+
"language_model.model.layers.9.self_attn.v_proj": {
|
| 526 |
+
"group_size": 32,
|
| 527 |
+
"bits": 8,
|
| 528 |
+
"mode": "mxfp8"
|
| 529 |
+
},
|
| 530 |
+
"language_model.model.layers.9.self_attn.o_proj": {
|
| 531 |
+
"group_size": 32,
|
| 532 |
+
"bits": 8,
|
| 533 |
+
"mode": "mxfp8"
|
| 534 |
+
},
|
| 535 |
+
"language_model.model.layers.9.mlp.switch_mlp.gate_proj": {
|
| 536 |
+
"group_size": 32,
|
| 537 |
+
"bits": 8,
|
| 538 |
+
"mode": "mxfp8"
|
| 539 |
+
},
|
| 540 |
+
"language_model.model.layers.9.mlp.switch_mlp.up_proj": {
|
| 541 |
+
"group_size": 32,
|
| 542 |
+
"bits": 8,
|
| 543 |
+
"mode": "mxfp8"
|
| 544 |
+
},
|
| 545 |
+
"language_model.model.layers.9.mlp.switch_mlp.down_proj": {
|
| 546 |
+
"group_size": 32,
|
| 547 |
+
"bits": 8,
|
| 548 |
+
"mode": "mxfp8"
|
| 549 |
+
},
|
| 550 |
+
"language_model.model.layers.9.mlp.shared_experts.gate_proj": {
|
| 551 |
+
"group_size": 32,
|
| 552 |
+
"bits": 8,
|
| 553 |
+
"mode": "mxfp8"
|
| 554 |
+
},
|
| 555 |
+
"language_model.model.layers.9.mlp.shared_experts.up_proj": {
|
| 556 |
+
"group_size": 32,
|
| 557 |
+
"bits": 8,
|
| 558 |
+
"mode": "mxfp8"
|
| 559 |
+
},
|
| 560 |
+
"language_model.model.layers.9.mlp.shared_experts.down_proj": {
|
| 561 |
+
"group_size": 32,
|
| 562 |
+
"bits": 8,
|
| 563 |
+
"mode": "mxfp8"
|
| 564 |
+
},
|
| 565 |
+
"language_model.model.layers.10.self_attn.q_proj": {
|
| 566 |
+
"group_size": 32,
|
| 567 |
+
"bits": 8,
|
| 568 |
+
"mode": "mxfp8"
|
| 569 |
+
},
|
| 570 |
+
"language_model.model.layers.10.self_attn.k_proj": {
|
| 571 |
+
"group_size": 32,
|
| 572 |
+
"bits": 8,
|
| 573 |
+
"mode": "mxfp8"
|
| 574 |
+
},
|
| 575 |
+
"language_model.model.layers.10.self_attn.v_proj": {
|
| 576 |
+
"group_size": 32,
|
| 577 |
+
"bits": 8,
|
| 578 |
+
"mode": "mxfp8"
|
| 579 |
+
},
|
| 580 |
+
"language_model.model.layers.10.self_attn.o_proj": {
|
| 581 |
+
"group_size": 32,
|
| 582 |
+
"bits": 8,
|
| 583 |
+
"mode": "mxfp8"
|
| 584 |
+
},
|
| 585 |
+
"language_model.model.layers.10.mlp.switch_mlp.gate_proj": {
|
| 586 |
+
"group_size": 32,
|
| 587 |
+
"bits": 8,
|
| 588 |
+
"mode": "mxfp8"
|
| 589 |
+
},
|
| 590 |
+
"language_model.model.layers.10.mlp.switch_mlp.up_proj": {
|
| 591 |
+
"group_size": 32,
|
| 592 |
+
"bits": 8,
|
| 593 |
+
"mode": "mxfp8"
|
| 594 |
+
},
|
| 595 |
+
"language_model.model.layers.10.mlp.switch_mlp.down_proj": {
|
| 596 |
+
"group_size": 32,
|
| 597 |
+
"bits": 8,
|
| 598 |
+
"mode": "mxfp8"
|
| 599 |
+
},
|
| 600 |
+
"language_model.model.layers.10.mlp.shared_experts.gate_proj": {
|
| 601 |
+
"group_size": 32,
|
| 602 |
+
"bits": 8,
|
| 603 |
+
"mode": "mxfp8"
|
| 604 |
+
},
|
| 605 |
+
"language_model.model.layers.10.mlp.shared_experts.up_proj": {
|
| 606 |
+
"group_size": 32,
|
| 607 |
+
"bits": 8,
|
| 608 |
+
"mode": "mxfp8"
|
| 609 |
+
},
|
| 610 |
+
"language_model.model.layers.10.mlp.shared_experts.down_proj": {
|
| 611 |
+
"group_size": 32,
|
| 612 |
+
"bits": 8,
|
| 613 |
+
"mode": "mxfp8"
|
| 614 |
+
},
|
| 615 |
+
"language_model.model.layers.11.self_attn.q_proj": {
|
| 616 |
+
"group_size": 32,
|
| 617 |
+
"bits": 8,
|
| 618 |
+
"mode": "mxfp8"
|
| 619 |
+
},
|
| 620 |
+
"language_model.model.layers.11.self_attn.k_proj": {
|
| 621 |
+
"group_size": 32,
|
| 622 |
+
"bits": 8,
|
| 623 |
+
"mode": "mxfp8"
|
| 624 |
+
},
|
| 625 |
+
"language_model.model.layers.11.self_attn.v_proj": {
|
| 626 |
+
"group_size": 32,
|
| 627 |
+
"bits": 8,
|
| 628 |
+
"mode": "mxfp8"
|
| 629 |
+
},
|
| 630 |
+
"language_model.model.layers.11.self_attn.o_proj": {
|
| 631 |
+
"group_size": 32,
|
| 632 |
+
"bits": 8,
|
| 633 |
+
"mode": "mxfp8"
|
| 634 |
+
},
|
| 635 |
+
"language_model.model.layers.11.mlp.switch_mlp.gate_proj": {
|
| 636 |
+
"group_size": 32,
|
| 637 |
+
"bits": 8,
|
| 638 |
+
"mode": "mxfp8"
|
| 639 |
+
},
|
| 640 |
+
"language_model.model.layers.11.mlp.switch_mlp.up_proj": {
|
| 641 |
+
"group_size": 32,
|
| 642 |
+
"bits": 8,
|
| 643 |
+
"mode": "mxfp8"
|
| 644 |
+
},
|
| 645 |
+
"language_model.model.layers.11.mlp.switch_mlp.down_proj": {
|
| 646 |
+
"group_size": 32,
|
| 647 |
+
"bits": 8,
|
| 648 |
+
"mode": "mxfp8"
|
| 649 |
+
},
|
| 650 |
+
"language_model.model.layers.11.mlp.shared_experts.gate_proj": {
|
| 651 |
+
"group_size": 32,
|
| 652 |
+
"bits": 8,
|
| 653 |
+
"mode": "mxfp8"
|
| 654 |
+
},
|
| 655 |
+
"language_model.model.layers.11.mlp.shared_experts.up_proj": {
|
| 656 |
+
"group_size": 32,
|
| 657 |
+
"bits": 8,
|
| 658 |
+
"mode": "mxfp8"
|
| 659 |
+
},
|
| 660 |
+
"language_model.model.layers.11.mlp.shared_experts.down_proj": {
|
| 661 |
+
"group_size": 32,
|
| 662 |
+
"bits": 8,
|
| 663 |
+
"mode": "mxfp8"
|
| 664 |
+
},
|
| 665 |
+
"language_model.lm_head": {
|
| 666 |
+
"group_size": 32,
|
| 667 |
+
"bits": 8,
|
| 668 |
+
"mode": "affine"
|
| 669 |
+
},
|
| 670 |
+
"projector.layers": {
|
| 671 |
+
"group_size": 32,
|
| 672 |
+
"bits": 8,
|
| 673 |
+
"mode": "mxfp8"
|
| 674 |
+
}
|
| 675 |
+
},
|
| 676 |
+
"quantization_config": {
|
| 677 |
+
"group_size": 32,
|
| 678 |
+
"bits": 8,
|
| 679 |
+
"mode": "mxfp8",
|
| 680 |
+
"language_model.model.embed_tokens": {
|
| 681 |
+
"group_size": 32,
|
| 682 |
+
"bits": 8,
|
| 683 |
+
"mode": "mxfp8"
|
| 684 |
+
},
|
| 685 |
+
"language_model.model.layers.0.self_attn.q_proj": {
|
| 686 |
+
"group_size": 32,
|
| 687 |
+
"bits": 8,
|
| 688 |
+
"mode": "mxfp8"
|
| 689 |
+
},
|
| 690 |
+
"language_model.model.layers.0.self_attn.k_proj": {
|
| 691 |
+
"group_size": 32,
|
| 692 |
+
"bits": 8,
|
| 693 |
+
"mode": "mxfp8"
|
| 694 |
+
},
|
| 695 |
+
"language_model.model.layers.0.self_attn.v_proj": {
|
| 696 |
+
"group_size": 32,
|
| 697 |
+
"bits": 8,
|
| 698 |
+
"mode": "mxfp8"
|
| 699 |
+
},
|
| 700 |
+
"language_model.model.layers.0.self_attn.o_proj": {
|
| 701 |
+
"group_size": 32,
|
| 702 |
+
"bits": 8,
|
| 703 |
+
"mode": "mxfp8"
|
| 704 |
+
},
|
| 705 |
+
"language_model.model.layers.0.mlp.gate_proj": {
|
| 706 |
+
"group_size": 32,
|
| 707 |
+
"bits": 8,
|
| 708 |
+
"mode": "mxfp8"
|
| 709 |
+
},
|
| 710 |
+
"language_model.model.layers.0.mlp.up_proj": {
|
| 711 |
+
"group_size": 32,
|
| 712 |
+
"bits": 8,
|
| 713 |
+
"mode": "mxfp8"
|
| 714 |
+
},
|
| 715 |
+
"language_model.model.layers.0.mlp.down_proj": {
|
| 716 |
+
"group_size": 32,
|
| 717 |
+
"bits": 8,
|
| 718 |
+
"mode": "mxfp8"
|
| 719 |
+
},
|
| 720 |
+
"language_model.model.layers.1.self_attn.q_proj": {
|
| 721 |
+
"group_size": 32,
|
| 722 |
+
"bits": 8,
|
| 723 |
+
"mode": "mxfp8"
|
| 724 |
+
},
|
| 725 |
+
"language_model.model.layers.1.self_attn.k_proj": {
|
| 726 |
+
"group_size": 32,
|
| 727 |
+
"bits": 8,
|
| 728 |
+
"mode": "mxfp8"
|
| 729 |
+
},
|
| 730 |
+
"language_model.model.layers.1.self_attn.v_proj": {
|
| 731 |
+
"group_size": 32,
|
| 732 |
+
"bits": 8,
|
| 733 |
+
"mode": "mxfp8"
|
| 734 |
+
},
|
| 735 |
+
"language_model.model.layers.1.self_attn.o_proj": {
|
| 736 |
+
"group_size": 32,
|
| 737 |
+
"bits": 8,
|
| 738 |
+
"mode": "mxfp8"
|
| 739 |
+
},
|
| 740 |
+
"language_model.model.layers.1.mlp.switch_mlp.gate_proj": {
|
| 741 |
+
"group_size": 32,
|
| 742 |
+
"bits": 8,
|
| 743 |
+
"mode": "mxfp8"
|
| 744 |
+
},
|
| 745 |
+
"language_model.model.layers.1.mlp.switch_mlp.up_proj": {
|
| 746 |
+
"group_size": 32,
|
| 747 |
+
"bits": 8,
|
| 748 |
+
"mode": "mxfp8"
|
| 749 |
+
},
|
| 750 |
+
"language_model.model.layers.1.mlp.switch_mlp.down_proj": {
|
| 751 |
+
"group_size": 32,
|
| 752 |
+
"bits": 8,
|
| 753 |
+
"mode": "mxfp8"
|
| 754 |
+
},
|
| 755 |
+
"language_model.model.layers.1.mlp.shared_experts.gate_proj": {
|
| 756 |
+
"group_size": 32,
|
| 757 |
+
"bits": 8,
|
| 758 |
+
"mode": "mxfp8"
|
| 759 |
+
},
|
| 760 |
+
"language_model.model.layers.1.mlp.shared_experts.up_proj": {
|
| 761 |
+
"group_size": 32,
|
| 762 |
+
"bits": 8,
|
| 763 |
+
"mode": "mxfp8"
|
| 764 |
+
},
|
| 765 |
+
"language_model.model.layers.1.mlp.shared_experts.down_proj": {
|
| 766 |
+
"group_size": 32,
|
| 767 |
+
"bits": 8,
|
| 768 |
+
"mode": "mxfp8"
|
| 769 |
+
},
|
| 770 |
+
"language_model.model.layers.2.self_attn.q_proj": {
|
| 771 |
+
"group_size": 32,
|
| 772 |
+
"bits": 8,
|
| 773 |
+
"mode": "mxfp8"
|
| 774 |
+
},
|
| 775 |
+
"language_model.model.layers.2.self_attn.k_proj": {
|
| 776 |
+
"group_size": 32,
|
| 777 |
+
"bits": 8,
|
| 778 |
+
"mode": "mxfp8"
|
| 779 |
+
},
|
| 780 |
+
"language_model.model.layers.2.self_attn.v_proj": {
|
| 781 |
+
"group_size": 32,
|
| 782 |
+
"bits": 8,
|
| 783 |
+
"mode": "mxfp8"
|
| 784 |
+
},
|
| 785 |
+
"language_model.model.layers.2.self_attn.o_proj": {
|
| 786 |
+
"group_size": 32,
|
| 787 |
+
"bits": 8,
|
| 788 |
+
"mode": "mxfp8"
|
| 789 |
+
},
|
| 790 |
+
"language_model.model.layers.2.mlp.switch_mlp.gate_proj": {
|
| 791 |
+
"group_size": 32,
|
| 792 |
+
"bits": 8,
|
| 793 |
+
"mode": "mxfp8"
|
| 794 |
+
},
|
| 795 |
+
"language_model.model.layers.2.mlp.switch_mlp.up_proj": {
|
| 796 |
+
"group_size": 32,
|
| 797 |
+
"bits": 8,
|
| 798 |
+
"mode": "mxfp8"
|
| 799 |
+
},
|
| 800 |
+
"language_model.model.layers.2.mlp.switch_mlp.down_proj": {
|
| 801 |
+
"group_size": 32,
|
| 802 |
+
"bits": 8,
|
| 803 |
+
"mode": "mxfp8"
|
| 804 |
+
},
|
| 805 |
+
"language_model.model.layers.2.mlp.shared_experts.gate_proj": {
|
| 806 |
+
"group_size": 32,
|
| 807 |
+
"bits": 8,
|
| 808 |
+
"mode": "mxfp8"
|
| 809 |
+
},
|
| 810 |
+
"language_model.model.layers.2.mlp.shared_experts.up_proj": {
|
| 811 |
+
"group_size": 32,
|
| 812 |
+
"bits": 8,
|
| 813 |
+
"mode": "mxfp8"
|
| 814 |
+
},
|
| 815 |
+
"language_model.model.layers.2.mlp.shared_experts.down_proj": {
|
| 816 |
+
"group_size": 32,
|
| 817 |
+
"bits": 8,
|
| 818 |
+
"mode": "mxfp8"
|
| 819 |
+
},
|
| 820 |
+
"language_model.model.layers.3.self_attn.q_proj": {
|
| 821 |
+
"group_size": 32,
|
| 822 |
+
"bits": 8,
|
| 823 |
+
"mode": "mxfp8"
|
| 824 |
+
},
|
| 825 |
+
"language_model.model.layers.3.self_attn.k_proj": {
|
| 826 |
+
"group_size": 32,
|
| 827 |
+
"bits": 8,
|
| 828 |
+
"mode": "mxfp8"
|
| 829 |
+
},
|
| 830 |
+
"language_model.model.layers.3.self_attn.v_proj": {
|
| 831 |
+
"group_size": 32,
|
| 832 |
+
"bits": 8,
|
| 833 |
+
"mode": "mxfp8"
|
| 834 |
+
},
|
| 835 |
+
"language_model.model.layers.3.self_attn.o_proj": {
|
| 836 |
+
"group_size": 32,
|
| 837 |
+
"bits": 8,
|
| 838 |
+
"mode": "mxfp8"
|
| 839 |
+
},
|
| 840 |
+
"language_model.model.layers.3.mlp.switch_mlp.gate_proj": {
|
| 841 |
+
"group_size": 32,
|
| 842 |
+
"bits": 8,
|
| 843 |
+
"mode": "mxfp8"
|
| 844 |
+
},
|
| 845 |
+
"language_model.model.layers.3.mlp.switch_mlp.up_proj": {
|
| 846 |
+
"group_size": 32,
|
| 847 |
+
"bits": 8,
|
| 848 |
+
"mode": "mxfp8"
|
| 849 |
+
},
|
| 850 |
+
"language_model.model.layers.3.mlp.switch_mlp.down_proj": {
|
| 851 |
+
"group_size": 32,
|
| 852 |
+
"bits": 8,
|
| 853 |
+
"mode": "mxfp8"
|
| 854 |
+
},
|
| 855 |
+
"language_model.model.layers.3.mlp.shared_experts.gate_proj": {
|
| 856 |
+
"group_size": 32,
|
| 857 |
+
"bits": 8,
|
| 858 |
+
"mode": "mxfp8"
|
| 859 |
+
},
|
| 860 |
+
"language_model.model.layers.3.mlp.shared_experts.up_proj": {
|
| 861 |
+
"group_size": 32,
|
| 862 |
+
"bits": 8,
|
| 863 |
+
"mode": "mxfp8"
|
| 864 |
+
},
|
| 865 |
+
"language_model.model.layers.3.mlp.shared_experts.down_proj": {
|
| 866 |
+
"group_size": 32,
|
| 867 |
+
"bits": 8,
|
| 868 |
+
"mode": "mxfp8"
|
| 869 |
+
},
|
| 870 |
+
"language_model.model.layers.4.self_attn.q_proj": {
|
| 871 |
+
"group_size": 32,
|
| 872 |
+
"bits": 8,
|
| 873 |
+
"mode": "mxfp8"
|
| 874 |
+
},
|
| 875 |
+
"language_model.model.layers.4.self_attn.k_proj": {
|
| 876 |
+
"group_size": 32,
|
| 877 |
+
"bits": 8,
|
| 878 |
+
"mode": "mxfp8"
|
| 879 |
+
},
|
| 880 |
+
"language_model.model.layers.4.self_attn.v_proj": {
|
| 881 |
+
"group_size": 32,
|
| 882 |
+
"bits": 8,
|
| 883 |
+
"mode": "mxfp8"
|
| 884 |
+
},
|
| 885 |
+
"language_model.model.layers.4.self_attn.o_proj": {
|
| 886 |
+
"group_size": 32,
|
| 887 |
+
"bits": 8,
|
| 888 |
+
"mode": "mxfp8"
|
| 889 |
+
},
|
| 890 |
+
"language_model.model.layers.4.mlp.switch_mlp.gate_proj": {
|
| 891 |
+
"group_size": 32,
|
| 892 |
+
"bits": 8,
|
| 893 |
+
"mode": "mxfp8"
|
| 894 |
+
},
|
| 895 |
+
"language_model.model.layers.4.mlp.switch_mlp.up_proj": {
|
| 896 |
+
"group_size": 32,
|
| 897 |
+
"bits": 8,
|
| 898 |
+
"mode": "mxfp8"
|
| 899 |
+
},
|
| 900 |
+
"language_model.model.layers.4.mlp.switch_mlp.down_proj": {
|
| 901 |
+
"group_size": 32,
|
| 902 |
+
"bits": 8,
|
| 903 |
+
"mode": "mxfp8"
|
| 904 |
+
},
|
| 905 |
+
"language_model.model.layers.4.mlp.shared_experts.gate_proj": {
|
| 906 |
+
"group_size": 32,
|
| 907 |
+
"bits": 8,
|
| 908 |
+
"mode": "mxfp8"
|
| 909 |
+
},
|
| 910 |
+
"language_model.model.layers.4.mlp.shared_experts.up_proj": {
|
| 911 |
+
"group_size": 32,
|
| 912 |
+
"bits": 8,
|
| 913 |
+
"mode": "mxfp8"
|
| 914 |
+
},
|
| 915 |
+
"language_model.model.layers.4.mlp.shared_experts.down_proj": {
|
| 916 |
+
"group_size": 32,
|
| 917 |
+
"bits": 8,
|
| 918 |
+
"mode": "mxfp8"
|
| 919 |
+
},
|
| 920 |
+
"language_model.model.layers.5.self_attn.q_proj": {
|
| 921 |
+
"group_size": 32,
|
| 922 |
+
"bits": 8,
|
| 923 |
+
"mode": "mxfp8"
|
| 924 |
+
},
|
| 925 |
+
"language_model.model.layers.5.self_attn.k_proj": {
|
| 926 |
+
"group_size": 32,
|
| 927 |
+
"bits": 8,
|
| 928 |
+
"mode": "mxfp8"
|
| 929 |
+
},
|
| 930 |
+
"language_model.model.layers.5.self_attn.v_proj": {
|
| 931 |
+
"group_size": 32,
|
| 932 |
+
"bits": 8,
|
| 933 |
+
"mode": "mxfp8"
|
| 934 |
+
},
|
| 935 |
+
"language_model.model.layers.5.self_attn.o_proj": {
|
| 936 |
+
"group_size": 32,
|
| 937 |
+
"bits": 8,
|
| 938 |
+
"mode": "mxfp8"
|
| 939 |
+
},
|
| 940 |
+
"language_model.model.layers.5.mlp.switch_mlp.gate_proj": {
|
| 941 |
+
"group_size": 32,
|
| 942 |
+
"bits": 8,
|
| 943 |
+
"mode": "mxfp8"
|
| 944 |
+
},
|
| 945 |
+
"language_model.model.layers.5.mlp.switch_mlp.up_proj": {
|
| 946 |
+
"group_size": 32,
|
| 947 |
+
"bits": 8,
|
| 948 |
+
"mode": "mxfp8"
|
| 949 |
+
},
|
| 950 |
+
"language_model.model.layers.5.mlp.switch_mlp.down_proj": {
|
| 951 |
+
"group_size": 32,
|
| 952 |
+
"bits": 8,
|
| 953 |
+
"mode": "mxfp8"
|
| 954 |
+
},
|
| 955 |
+
"language_model.model.layers.5.mlp.shared_experts.gate_proj": {
|
| 956 |
+
"group_size": 32,
|
| 957 |
+
"bits": 8,
|
| 958 |
+
"mode": "mxfp8"
|
| 959 |
+
},
|
| 960 |
+
"language_model.model.layers.5.mlp.shared_experts.up_proj": {
|
| 961 |
+
"group_size": 32,
|
| 962 |
+
"bits": 8,
|
| 963 |
+
"mode": "mxfp8"
|
| 964 |
+
},
|
| 965 |
+
"language_model.model.layers.5.mlp.shared_experts.down_proj": {
|
| 966 |
+
"group_size": 32,
|
| 967 |
+
"bits": 8,
|
| 968 |
+
"mode": "mxfp8"
|
| 969 |
+
},
|
| 970 |
+
"language_model.model.layers.6.self_attn.q_proj": {
|
| 971 |
+
"group_size": 32,
|
| 972 |
+
"bits": 8,
|
| 973 |
+
"mode": "mxfp8"
|
| 974 |
+
},
|
| 975 |
+
"language_model.model.layers.6.self_attn.k_proj": {
|
| 976 |
+
"group_size": 32,
|
| 977 |
+
"bits": 8,
|
| 978 |
+
"mode": "mxfp8"
|
| 979 |
+
},
|
| 980 |
+
"language_model.model.layers.6.self_attn.v_proj": {
|
| 981 |
+
"group_size": 32,
|
| 982 |
+
"bits": 8,
|
| 983 |
+
"mode": "mxfp8"
|
| 984 |
+
},
|
| 985 |
+
"language_model.model.layers.6.self_attn.o_proj": {
|
| 986 |
+
"group_size": 32,
|
| 987 |
+
"bits": 8,
|
| 988 |
+
"mode": "mxfp8"
|
| 989 |
+
},
|
| 990 |
+
"language_model.model.layers.6.mlp.switch_mlp.gate_proj": {
|
| 991 |
+
"group_size": 32,
|
| 992 |
+
"bits": 8,
|
| 993 |
+
"mode": "mxfp8"
|
| 994 |
+
},
|
| 995 |
+
"language_model.model.layers.6.mlp.switch_mlp.up_proj": {
|
| 996 |
+
"group_size": 32,
|
| 997 |
+
"bits": 8,
|
| 998 |
+
"mode": "mxfp8"
|
| 999 |
+
},
|
| 1000 |
+
"language_model.model.layers.6.mlp.switch_mlp.down_proj": {
|
| 1001 |
+
"group_size": 32,
|
| 1002 |
+
"bits": 8,
|
| 1003 |
+
"mode": "mxfp8"
|
| 1004 |
+
},
|
| 1005 |
+
"language_model.model.layers.6.mlp.shared_experts.gate_proj": {
|
| 1006 |
+
"group_size": 32,
|
| 1007 |
+
"bits": 8,
|
| 1008 |
+
"mode": "mxfp8"
|
| 1009 |
+
},
|
| 1010 |
+
"language_model.model.layers.6.mlp.shared_experts.up_proj": {
|
| 1011 |
+
"group_size": 32,
|
| 1012 |
+
"bits": 8,
|
| 1013 |
+
"mode": "mxfp8"
|
| 1014 |
+
},
|
| 1015 |
+
"language_model.model.layers.6.mlp.shared_experts.down_proj": {
|
| 1016 |
+
"group_size": 32,
|
| 1017 |
+
"bits": 8,
|
| 1018 |
+
"mode": "mxfp8"
|
| 1019 |
+
},
|
| 1020 |
+
"language_model.model.layers.7.self_attn.q_proj": {
|
| 1021 |
+
"group_size": 32,
|
| 1022 |
+
"bits": 8,
|
| 1023 |
+
"mode": "mxfp8"
|
| 1024 |
+
},
|
| 1025 |
+
"language_model.model.layers.7.self_attn.k_proj": {
|
| 1026 |
+
"group_size": 32,
|
| 1027 |
+
"bits": 8,
|
| 1028 |
+
"mode": "mxfp8"
|
| 1029 |
+
},
|
| 1030 |
+
"language_model.model.layers.7.self_attn.v_proj": {
|
| 1031 |
+
"group_size": 32,
|
| 1032 |
+
"bits": 8,
|
| 1033 |
+
"mode": "mxfp8"
|
| 1034 |
+
},
|
| 1035 |
+
"language_model.model.layers.7.self_attn.o_proj": {
|
| 1036 |
+
"group_size": 32,
|
| 1037 |
+
"bits": 8,
|
| 1038 |
+
"mode": "mxfp8"
|
| 1039 |
+
},
|
| 1040 |
+
"language_model.model.layers.7.mlp.switch_mlp.gate_proj": {
|
| 1041 |
+
"group_size": 32,
|
| 1042 |
+
"bits": 8,
|
| 1043 |
+
"mode": "mxfp8"
|
| 1044 |
+
},
|
| 1045 |
+
"language_model.model.layers.7.mlp.switch_mlp.up_proj": {
|
| 1046 |
+
"group_size": 32,
|
| 1047 |
+
"bits": 8,
|
| 1048 |
+
"mode": "mxfp8"
|
| 1049 |
+
},
|
| 1050 |
+
"language_model.model.layers.7.mlp.switch_mlp.down_proj": {
|
| 1051 |
+
"group_size": 32,
|
| 1052 |
+
"bits": 8,
|
| 1053 |
+
"mode": "mxfp8"
|
| 1054 |
+
},
|
| 1055 |
+
"language_model.model.layers.7.mlp.shared_experts.gate_proj": {
|
| 1056 |
+
"group_size": 32,
|
| 1057 |
+
"bits": 8,
|
| 1058 |
+
"mode": "mxfp8"
|
| 1059 |
+
},
|
| 1060 |
+
"language_model.model.layers.7.mlp.shared_experts.up_proj": {
|
| 1061 |
+
"group_size": 32,
|
| 1062 |
+
"bits": 8,
|
| 1063 |
+
"mode": "mxfp8"
|
| 1064 |
+
},
|
| 1065 |
+
"language_model.model.layers.7.mlp.shared_experts.down_proj": {
|
| 1066 |
+
"group_size": 32,
|
| 1067 |
+
"bits": 8,
|
| 1068 |
+
"mode": "mxfp8"
|
| 1069 |
+
},
|
| 1070 |
+
"language_model.model.layers.8.self_attn.q_proj": {
|
| 1071 |
+
"group_size": 32,
|
| 1072 |
+
"bits": 8,
|
| 1073 |
+
"mode": "mxfp8"
|
| 1074 |
+
},
|
| 1075 |
+
"language_model.model.layers.8.self_attn.k_proj": {
|
| 1076 |
+
"group_size": 32,
|
| 1077 |
+
"bits": 8,
|
| 1078 |
+
"mode": "mxfp8"
|
| 1079 |
+
},
|
| 1080 |
+
"language_model.model.layers.8.self_attn.v_proj": {
|
| 1081 |
+
"group_size": 32,
|
| 1082 |
+
"bits": 8,
|
| 1083 |
+
"mode": "mxfp8"
|
| 1084 |
+
},
|
| 1085 |
+
"language_model.model.layers.8.self_attn.o_proj": {
|
| 1086 |
+
"group_size": 32,
|
| 1087 |
+
"bits": 8,
|
| 1088 |
+
"mode": "mxfp8"
|
| 1089 |
+
},
|
| 1090 |
+
"language_model.model.layers.8.mlp.switch_mlp.gate_proj": {
|
| 1091 |
+
"group_size": 32,
|
| 1092 |
+
"bits": 8,
|
| 1093 |
+
"mode": "mxfp8"
|
| 1094 |
+
},
|
| 1095 |
+
"language_model.model.layers.8.mlp.switch_mlp.up_proj": {
|
| 1096 |
+
"group_size": 32,
|
| 1097 |
+
"bits": 8,
|
| 1098 |
+
"mode": "mxfp8"
|
| 1099 |
+
},
|
| 1100 |
+
"language_model.model.layers.8.mlp.switch_mlp.down_proj": {
|
| 1101 |
+
"group_size": 32,
|
| 1102 |
+
"bits": 8,
|
| 1103 |
+
"mode": "mxfp8"
|
| 1104 |
+
},
|
| 1105 |
+
"language_model.model.layers.8.mlp.shared_experts.gate_proj": {
|
| 1106 |
+
"group_size": 32,
|
| 1107 |
+
"bits": 8,
|
| 1108 |
+
"mode": "mxfp8"
|
| 1109 |
+
},
|
| 1110 |
+
"language_model.model.layers.8.mlp.shared_experts.up_proj": {
|
| 1111 |
+
"group_size": 32,
|
| 1112 |
+
"bits": 8,
|
| 1113 |
+
"mode": "mxfp8"
|
| 1114 |
+
},
|
| 1115 |
+
"language_model.model.layers.8.mlp.shared_experts.down_proj": {
|
| 1116 |
+
"group_size": 32,
|
| 1117 |
+
"bits": 8,
|
| 1118 |
+
"mode": "mxfp8"
|
| 1119 |
+
},
|
| 1120 |
+
"language_model.model.layers.9.self_attn.q_proj": {
|
| 1121 |
+
"group_size": 32,
|
| 1122 |
+
"bits": 8,
|
| 1123 |
+
"mode": "mxfp8"
|
| 1124 |
+
},
|
| 1125 |
+
"language_model.model.layers.9.self_attn.k_proj": {
|
| 1126 |
+
"group_size": 32,
|
| 1127 |
+
"bits": 8,
|
| 1128 |
+
"mode": "mxfp8"
|
| 1129 |
+
},
|
| 1130 |
+
"language_model.model.layers.9.self_attn.v_proj": {
|
| 1131 |
+
"group_size": 32,
|
| 1132 |
+
"bits": 8,
|
| 1133 |
+
"mode": "mxfp8"
|
| 1134 |
+
},
|
| 1135 |
+
"language_model.model.layers.9.self_attn.o_proj": {
|
| 1136 |
+
"group_size": 32,
|
| 1137 |
+
"bits": 8,
|
| 1138 |
+
"mode": "mxfp8"
|
| 1139 |
+
},
|
| 1140 |
+
"language_model.model.layers.9.mlp.switch_mlp.gate_proj": {
|
| 1141 |
+
"group_size": 32,
|
| 1142 |
+
"bits": 8,
|
| 1143 |
+
"mode": "mxfp8"
|
| 1144 |
+
},
|
| 1145 |
+
"language_model.model.layers.9.mlp.switch_mlp.up_proj": {
|
| 1146 |
+
"group_size": 32,
|
| 1147 |
+
"bits": 8,
|
| 1148 |
+
"mode": "mxfp8"
|
| 1149 |
+
},
|
| 1150 |
+
"language_model.model.layers.9.mlp.switch_mlp.down_proj": {
|
| 1151 |
+
"group_size": 32,
|
| 1152 |
+
"bits": 8,
|
| 1153 |
+
"mode": "mxfp8"
|
| 1154 |
+
},
|
| 1155 |
+
"language_model.model.layers.9.mlp.shared_experts.gate_proj": {
|
| 1156 |
+
"group_size": 32,
|
| 1157 |
+
"bits": 8,
|
| 1158 |
+
"mode": "mxfp8"
|
| 1159 |
+
},
|
| 1160 |
+
"language_model.model.layers.9.mlp.shared_experts.up_proj": {
|
| 1161 |
+
"group_size": 32,
|
| 1162 |
+
"bits": 8,
|
| 1163 |
+
"mode": "mxfp8"
|
| 1164 |
+
},
|
| 1165 |
+
"language_model.model.layers.9.mlp.shared_experts.down_proj": {
|
| 1166 |
+
"group_size": 32,
|
| 1167 |
+
"bits": 8,
|
| 1168 |
+
"mode": "mxfp8"
|
| 1169 |
+
},
|
| 1170 |
+
"language_model.model.layers.10.self_attn.q_proj": {
|
| 1171 |
+
"group_size": 32,
|
| 1172 |
+
"bits": 8,
|
| 1173 |
+
"mode": "mxfp8"
|
| 1174 |
+
},
|
| 1175 |
+
"language_model.model.layers.10.self_attn.k_proj": {
|
| 1176 |
+
"group_size": 32,
|
| 1177 |
+
"bits": 8,
|
| 1178 |
+
"mode": "mxfp8"
|
| 1179 |
+
},
|
| 1180 |
+
"language_model.model.layers.10.self_attn.v_proj": {
|
| 1181 |
+
"group_size": 32,
|
| 1182 |
+
"bits": 8,
|
| 1183 |
+
"mode": "mxfp8"
|
| 1184 |
+
},
|
| 1185 |
+
"language_model.model.layers.10.self_attn.o_proj": {
|
| 1186 |
+
"group_size": 32,
|
| 1187 |
+
"bits": 8,
|
| 1188 |
+
"mode": "mxfp8"
|
| 1189 |
+
},
|
| 1190 |
+
"language_model.model.layers.10.mlp.switch_mlp.gate_proj": {
|
| 1191 |
+
"group_size": 32,
|
| 1192 |
+
"bits": 8,
|
| 1193 |
+
"mode": "mxfp8"
|
| 1194 |
+
},
|
| 1195 |
+
"language_model.model.layers.10.mlp.switch_mlp.up_proj": {
|
| 1196 |
+
"group_size": 32,
|
| 1197 |
+
"bits": 8,
|
| 1198 |
+
"mode": "mxfp8"
|
| 1199 |
+
},
|
| 1200 |
+
"language_model.model.layers.10.mlp.switch_mlp.down_proj": {
|
| 1201 |
+
"group_size": 32,
|
| 1202 |
+
"bits": 8,
|
| 1203 |
+
"mode": "mxfp8"
|
| 1204 |
+
},
|
| 1205 |
+
"language_model.model.layers.10.mlp.shared_experts.gate_proj": {
|
| 1206 |
+
"group_size": 32,
|
| 1207 |
+
"bits": 8,
|
| 1208 |
+
"mode": "mxfp8"
|
| 1209 |
+
},
|
| 1210 |
+
"language_model.model.layers.10.mlp.shared_experts.up_proj": {
|
| 1211 |
+
"group_size": 32,
|
| 1212 |
+
"bits": 8,
|
| 1213 |
+
"mode": "mxfp8"
|
| 1214 |
+
},
|
| 1215 |
+
"language_model.model.layers.10.mlp.shared_experts.down_proj": {
|
| 1216 |
+
"group_size": 32,
|
| 1217 |
+
"bits": 8,
|
| 1218 |
+
"mode": "mxfp8"
|
| 1219 |
+
},
|
| 1220 |
+
"language_model.model.layers.11.self_attn.q_proj": {
|
| 1221 |
+
"group_size": 32,
|
| 1222 |
+
"bits": 8,
|
| 1223 |
+
"mode": "mxfp8"
|
| 1224 |
+
},
|
| 1225 |
+
"language_model.model.layers.11.self_attn.k_proj": {
|
| 1226 |
+
"group_size": 32,
|
| 1227 |
+
"bits": 8,
|
| 1228 |
+
"mode": "mxfp8"
|
| 1229 |
+
},
|
| 1230 |
+
"language_model.model.layers.11.self_attn.v_proj": {
|
| 1231 |
+
"group_size": 32,
|
| 1232 |
+
"bits": 8,
|
| 1233 |
+
"mode": "mxfp8"
|
| 1234 |
+
},
|
| 1235 |
+
"language_model.model.layers.11.self_attn.o_proj": {
|
| 1236 |
+
"group_size": 32,
|
| 1237 |
+
"bits": 8,
|
| 1238 |
+
"mode": "mxfp8"
|
| 1239 |
+
},
|
| 1240 |
+
"language_model.model.layers.11.mlp.switch_mlp.gate_proj": {
|
| 1241 |
+
"group_size": 32,
|
| 1242 |
+
"bits": 8,
|
| 1243 |
+
"mode": "mxfp8"
|
| 1244 |
+
},
|
| 1245 |
+
"language_model.model.layers.11.mlp.switch_mlp.up_proj": {
|
| 1246 |
+
"group_size": 32,
|
| 1247 |
+
"bits": 8,
|
| 1248 |
+
"mode": "mxfp8"
|
| 1249 |
+
},
|
| 1250 |
+
"language_model.model.layers.11.mlp.switch_mlp.down_proj": {
|
| 1251 |
+
"group_size": 32,
|
| 1252 |
+
"bits": 8,
|
| 1253 |
+
"mode": "mxfp8"
|
| 1254 |
+
},
|
| 1255 |
+
"language_model.model.layers.11.mlp.shared_experts.gate_proj": {
|
| 1256 |
+
"group_size": 32,
|
| 1257 |
+
"bits": 8,
|
| 1258 |
+
"mode": "mxfp8"
|
| 1259 |
+
},
|
| 1260 |
+
"language_model.model.layers.11.mlp.shared_experts.up_proj": {
|
| 1261 |
+
"group_size": 32,
|
| 1262 |
+
"bits": 8,
|
| 1263 |
+
"mode": "mxfp8"
|
| 1264 |
+
},
|
| 1265 |
+
"language_model.model.layers.11.mlp.shared_experts.down_proj": {
|
| 1266 |
+
"group_size": 32,
|
| 1267 |
+
"bits": 8,
|
| 1268 |
+
"mode": "mxfp8"
|
| 1269 |
+
},
|
| 1270 |
+
"language_model.lm_head": {
|
| 1271 |
+
"group_size": 32,
|
| 1272 |
+
"bits": 8,
|
| 1273 |
+
"mode": "affine"
|
| 1274 |
+
},
|
| 1275 |
+
"projector.layers": {
|
| 1276 |
+
"group_size": 32,
|
| 1277 |
+
"bits": 8,
|
| 1278 |
+
"mode": "mxfp8"
|
| 1279 |
+
}
|
| 1280 |
+
},
|
| 1281 |
+
"rm_head": false,
|
| 1282 |
+
"sliding_window": 128,
|
| 1283 |
+
"sliding_window_size": 128,
|
| 1284 |
+
"tile_tag": "2D",
|
| 1285 |
+
"topk_group": 1,
|
| 1286 |
+
"topk_method": "greedy",
|
| 1287 |
+
"transformers_version": "4.46.3",
|
| 1288 |
+
"use_mla": false,
|
| 1289 |
+
"v_head_dim": 128,
|
| 1290 |
+
"vision_config": {
|
| 1291 |
+
"image_size": 1024,
|
| 1292 |
+
"mlp_ratio": 3.7362,
|
| 1293 |
+
"model_name": "deeplip_b_l",
|
| 1294 |
+
"model_type": "vision",
|
| 1295 |
+
"width": {
|
| 1296 |
+
"clip-l-14-224": {
|
| 1297 |
+
"heads": 16,
|
| 1298 |
+
"image_size": 224,
|
| 1299 |
+
"layers": 24,
|
| 1300 |
+
"patch_size": 14,
|
| 1301 |
+
"width": 1024
|
| 1302 |
+
},
|
| 1303 |
+
"sam_vit_b": {
|
| 1304 |
+
"downsample_channels": [
|
| 1305 |
+
512,
|
| 1306 |
+
1024
|
| 1307 |
+
],
|
| 1308 |
+
"global_attn_indexes": [
|
| 1309 |
+
2,
|
| 1310 |
+
5,
|
| 1311 |
+
8,
|
| 1312 |
+
11
|
| 1313 |
+
],
|
| 1314 |
+
"heads": 12,
|
| 1315 |
+
"layers": 12,
|
| 1316 |
+
"width": 768
|
| 1317 |
+
}
|
| 1318 |
+
}
|
| 1319 |
+
},
|
| 1320 |
+
"vocab_size": 129280
|
| 1321 |
+
}
|
docs/PRD.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Product Requirements Document: Verified OCR-Aware Unlimited-OCR for MLX
|
| 2 |
+
|
| 3 |
+
Status: Approved for implementation
|
| 4 |
+
Owner: AutomatosX
|
| 5 |
+
Target release: `AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8`
|
| 6 |
+
|
| 7 |
+
## 1. Problem
|
| 8 |
+
|
| 9 |
+
The current project improves inference ergonomics around an existing third-party
|
| 10 |
+
MXFP8 checkpoint, but it does not yet prove that AutomatosX produced distinct,
|
| 11 |
+
OCR-aware model weights. The existing precision map has no measured sensitivity
|
| 12 |
+
overrides, the evaluation dataset is empty, and the legacy upload path can
|
| 13 |
+
republish source weights with only metadata changes.
|
| 14 |
+
|
| 15 |
+
Users need an Apple Silicon checkpoint whose model-level optimization is
|
| 16 |
+
reproducible, measurable, and clearly separated from application tooling.
|
| 17 |
+
|
| 18 |
+
## 2. Product outcome
|
| 19 |
+
|
| 20 |
+
Produce and publish a distinct Unlimited-OCR MLX checkpoint converted from the
|
| 21 |
+
upstream Baidu BF16 model. Quantization decisions must be derived from OCR
|
| 22 |
+
sensitivity measurements, and publication must be blocked unless quality,
|
| 23 |
+
performance, provenance, and R-SWA checks pass.
|
| 24 |
+
|
| 25 |
+
## 3. Users and jobs
|
| 26 |
+
|
| 27 |
+
- Apple Silicon developers converting PDFs and images to text or Markdown.
|
| 28 |
+
- RAG teams requiring local document ingestion with predictable memory use.
|
| 29 |
+
- Model engineers who need reproducible quantization and benchmark evidence.
|
| 30 |
+
- Reviewers who need to distinguish new weights from repackaged checkpoints.
|
| 31 |
+
|
| 32 |
+
## 4. Goals
|
| 33 |
+
|
| 34 |
+
1. Start from an unquantized BF16 `baidu/Unlimited-OCR` checkpoint.
|
| 35 |
+
2. Measure BF16 baseline quality before quantization.
|
| 36 |
+
3. Measure quantization sensitivity for each supported module group.
|
| 37 |
+
4. Keep sensitive groups in BF16, quantize eligible groups to MXFP8, and allow
|
| 38 |
+
a measured 8-bit affine exception when it is the only configuration that
|
| 39 |
+
satisfies both quality and throughput gates.
|
| 40 |
+
5. Produce a candidate checkpoint with a deterministic provenance manifest.
|
| 41 |
+
6. Compare the candidate with both BF16 and the Sahil MXFP8 reference.
|
| 42 |
+
7. Verify native Unlimited-OCR R-SWA behavior with an 8K-or-longer run.
|
| 43 |
+
8. Publish atomically to the AutomatosX namespace only after all gates pass.
|
| 44 |
+
|
| 45 |
+
## 5. Non-goals
|
| 46 |
+
|
| 47 |
+
- Retraining the Baidu base model or claiming a new foundation model.
|
| 48 |
+
- LoRA fine-tuning for a specific document domain.
|
| 49 |
+
- Claiming upstream paper scores without reproducing them.
|
| 50 |
+
- Publishing an unvalidated checkpoint merely because conversion completed.
|
| 51 |
+
- Implementing true multi-image one-shot PDF inference in this release.
|
| 52 |
+
|
| 53 |
+
## 6. Functional requirements
|
| 54 |
+
|
| 55 |
+
### FR-1 Source verification
|
| 56 |
+
|
| 57 |
+
- Reject an already quantized model as the sensitivity or conversion source.
|
| 58 |
+
- Record source repository/path, revision when available, config digest, and
|
| 59 |
+
weight-file SHA-256 values.
|
| 60 |
+
|
| 61 |
+
### FR-2 Evaluation data
|
| 62 |
+
|
| 63 |
+
- Validate image/ground-truth pairs before allocating the model.
|
| 64 |
+
- Record dataset manifest digest and category counts.
|
| 65 |
+
- Release evaluation must include text, digits, tables, and CJK content.
|
| 66 |
+
- Synthetic-only evaluation may be used for development, but not labelled as a
|
| 67 |
+
public accuracy benchmark.
|
| 68 |
+
|
| 69 |
+
### FR-3 Sensitivity analysis
|
| 70 |
+
|
| 71 |
+
- Evaluate BF16 once, then quantize one supported group at a time.
|
| 72 |
+
- Record matched concrete module paths and CER, digit-CER, and table-score
|
| 73 |
+
deltas for every group.
|
| 74 |
+
- Fail if any requested group cannot be evaluated.
|
| 75 |
+
|
| 76 |
+
### FR-4 Precision-map generation
|
| 77 |
+
|
| 78 |
+
- Generate executable top-level precision rules; metadata-only nested rules do
|
| 79 |
+
not count.
|
| 80 |
+
- Promote a group to BF16 if any configured quality threshold is exceeded.
|
| 81 |
+
- Preserve vision, projector, embeddings, normalisation, LM head, and MoE gate
|
| 82 |
+
in BF16 unless explicit evidence supports quantizing them.
|
| 83 |
+
- Select any exception through a recorded multi-candidate calibration; a manual
|
| 84 |
+
precision-map edit is not sufficient release evidence.
|
| 85 |
+
|
| 86 |
+
### FR-5 Conversion
|
| 87 |
+
|
| 88 |
+
- Convert from BF16 in a staging directory.
|
| 89 |
+
- Fail if no module is quantized, a requested rule matches nothing, or no model
|
| 90 |
+
weights are produced.
|
| 91 |
+
- Write `precision_map.json` and `quantization_summary.json` beside the weights.
|
| 92 |
+
- Record every quantized module's actual mode; the released configuration uses
|
| 93 |
+
MXFP8 for 119 modules and affine INT8 for the LM head.
|
| 94 |
+
|
| 95 |
+
### FR-6 Comparative validation
|
| 96 |
+
|
| 97 |
+
- Benchmark BF16, Sahil MXFP8, and the candidate with the same inputs, prompts,
|
| 98 |
+
token budgets, and software versions.
|
| 99 |
+
- Store per-sample results as well as aggregates.
|
| 100 |
+
|
| 101 |
+
### FR-7 Release gates
|
| 102 |
+
|
| 103 |
+
Publication requires all of the following:
|
| 104 |
+
|
| 105 |
+
- Candidate weight SHA-256 differs from the Sahil reference.
|
| 106 |
+
- Candidate mean CER is no more than 0.01 absolute above BF16.
|
| 107 |
+
- Candidate mean CER is no worse than the Sahil reference by more than 0.005.
|
| 108 |
+
- Digit CER degradation versus BF16 is no more than 0.01 absolute.
|
| 109 |
+
- Table score degradation versus BF16 is no more than 0.01 absolute.
|
| 110 |
+
- Candidate decode throughput is at least 90% of the Sahil reference.
|
| 111 |
+
- Candidate model weights are no larger than 4.5 GB.
|
| 112 |
+
- R-SWA validation reaches at least 8192 generated tokens with bounded-cache
|
| 113 |
+
evidence, or explicitly reports an inconclusive gate and blocks release.
|
| 114 |
+
- Every required artifact and digest is present.
|
| 115 |
+
|
| 116 |
+
### FR-8 Publication
|
| 117 |
+
|
| 118 |
+
- Create or update `AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8` atomically.
|
| 119 |
+
- Upload only the validated candidate weights, metadata, model card, license,
|
| 120 |
+
inference package, benchmark summaries, and release manifest.
|
| 121 |
+
- Never silently fall back to uploading Sahil weights.
|
| 122 |
+
|
| 123 |
+
### FR-9 Claims
|
| 124 |
+
|
| 125 |
+
- The model card must distinguish upstream paper results, third-party reference
|
| 126 |
+
results, and AutomatosX measurements.
|
| 127 |
+
- Failed, skipped, estimated, and inconclusive measurements must be labelled.
|
| 128 |
+
|
| 129 |
+
## 7. Success metrics
|
| 130 |
+
|
| 131 |
+
- A public Hugging Face model repository contains distinct candidate weights.
|
| 132 |
+
- `release_manifest.json` has `release_approved: true` and every gate result.
|
| 133 |
+
- A clean environment can reproduce conversion from the recorded source.
|
| 134 |
+
- The model loads through `mlx-vlm` using native `model_type=unlimited-ocr`.
|
| 135 |
+
- CLI single-image OCR succeeds with the published model ID.
|
| 136 |
+
|
| 137 |
+
## 8. Risks
|
| 138 |
+
|
| 139 |
+
- Evaluation data may be too small or biased to identify fragile layers.
|
| 140 |
+
- Group-level sensitivity can hide individual-layer outliers.
|
| 141 |
+
- MXFP8 support or module paths may change between `mlx-vlm` versions.
|
| 142 |
+
- Long-output documents may terminate naturally before the R-SWA test length.
|
| 143 |
+
- Public benchmark licenses may restrict redistribution of evaluation images.
|
| 144 |
+
|
| 145 |
+
Mitigations include pinned versions, digest-based provenance, fail-closed gates,
|
| 146 |
+
per-sample results, and release notes that state dataset limitations.
|
| 147 |
+
|
| 148 |
+
## 9. Rollout
|
| 149 |
+
|
| 150 |
+
1. Development: unit tests and synthetic smoke data.
|
| 151 |
+
2. Candidate: BF16 sensitivity, conversion, and private/local validation.
|
| 152 |
+
3. Release: approve manifest, create public model repository, upload atomically.
|
| 153 |
+
4. Post-release: load the exact remote revision and run a final OCR smoke test.
|
docs/TECHNICAL_SPEC.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Technical Specification: OCR-Aware Mixed-Precision Release Pipeline
|
| 2 |
+
|
| 3 |
+
Status: Implementation specification
|
| 4 |
+
Target repository: `AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8`
|
| 5 |
+
|
| 6 |
+
## 1. System overview
|
| 7 |
+
|
| 8 |
+
```text
|
| 9 |
+
BF16 Baidu checkpoint
|
| 10 |
+
|
|
| 11 |
+
v
|
| 12 |
+
source + dataset preflight
|
| 13 |
+
|
|
| 14 |
+
v
|
| 15 |
+
BF16 baseline evaluation
|
| 16 |
+
|
|
| 17 |
+
v
|
| 18 |
+
one-group-at-a-time MXFP8 sensitivity
|
| 19 |
+
|
|
| 20 |
+
v
|
| 21 |
+
joint quality/throughput head calibration
|
| 22 |
+
|
|
| 23 |
+
v
|
| 24 |
+
generated executable precision map
|
| 25 |
+
|
|
| 26 |
+
v
|
| 27 |
+
staged BF16 -> mixed MXFP8 conversion
|
| 28 |
+
|
|
| 29 |
+
v
|
| 30 |
+
candidate vs BF16 vs Sahil validation
|
| 31 |
+
|
|
| 32 |
+
v
|
| 33 |
+
accuracy + performance + R-SWA release gates
|
| 34 |
+
|
|
| 35 |
+
v
|
| 36 |
+
content-addressed release manifest
|
| 37 |
+
|
|
| 38 |
+
v
|
| 39 |
+
atomic Hugging Face publication
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
## 2. Components
|
| 43 |
+
|
| 44 |
+
### 2.1 Dataset validator
|
| 45 |
+
|
| 46 |
+
Input layout:
|
| 47 |
+
|
| 48 |
+
```text
|
| 49 |
+
eval_dir/
|
| 50 |
+
manifest.json
|
| 51 |
+
images/<sample>.<ext>
|
| 52 |
+
ground_truth/<sample>.txt
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
The validator checks unique stems, complete pairs, non-empty ground truth,
|
| 56 |
+
manifest consistency, category counts, and a SHA-256 digest over the manifest
|
| 57 |
+
and referenced files. Validation occurs before importing MLX.
|
| 58 |
+
|
| 59 |
+
### 2.2 Sensitivity runner
|
| 60 |
+
|
| 61 |
+
`quantization/layer_sensitivity.py` loads the unquantized model and computes one
|
| 62 |
+
BF16 baseline. For each module group it reloads BF16, quantizes only the group,
|
| 63 |
+
and records:
|
| 64 |
+
|
| 65 |
+
- matched concrete module paths;
|
| 66 |
+
- sample count and task coverage;
|
| 67 |
+
- CER, digit CER, and table score;
|
| 68 |
+
- deltas from BF16;
|
| 69 |
+
- elapsed time and status.
|
| 70 |
+
|
| 71 |
+
The runner rejects a source config containing `quantization` or
|
| 72 |
+
`quantization_config`.
|
| 73 |
+
|
| 74 |
+
### 2.3 Precision-map generator
|
| 75 |
+
|
| 76 |
+
The generator reads `layer_groups` from the sensitivity result and the approved
|
| 77 |
+
calibration result. It starts from the conservative base map, promotes
|
| 78 |
+
sensitive or failed groups to BF16, and applies only calibration overrides that
|
| 79 |
+
were selected by existing release thresholds.
|
| 80 |
+
Generated rules are top-level executable entries. Metadata is stored under keys
|
| 81 |
+
beginning with `_`, which the converter ignores.
|
| 82 |
+
|
| 83 |
+
Rule precedence is:
|
| 84 |
+
|
| 85 |
+
1. exact concrete module path;
|
| 86 |
+
2. more-specific wildcard path;
|
| 87 |
+
3. general wildcard path;
|
| 88 |
+
4. unlisted modules default to BF16.
|
| 89 |
+
|
| 90 |
+
### 2.4 Converter
|
| 91 |
+
|
| 92 |
+
`quantization/mixed_precision_convert.py` calls the pinned `mlx-vlm` converter
|
| 93 |
+
with per-module quantization settings. The main model uses MXFP8 with eight data
|
| 94 |
+
bits and group size 32; the calibrated LM head uses 8-bit affine quantization
|
| 95 |
+
with group size 32. It writes into a new sibling staging directory and
|
| 96 |
+
atomically renames it only after verifying:
|
| 97 |
+
|
| 98 |
+
- at least one module was quantized;
|
| 99 |
+
- every requested MXFP8 or affine8 rule matched;
|
| 100 |
+
- at least one Safetensors file exists;
|
| 101 |
+
- model metadata selects native Unlimited-OCR;
|
| 102 |
+
- `precision_map.json` and `quantization_summary.json` were written.
|
| 103 |
+
|
| 104 |
+
The requested final output directory must not exist.
|
| 105 |
+
|
| 106 |
+
### 2.5 Comparative benchmark runner
|
| 107 |
+
|
| 108 |
+
For each model (`bf16`, `reference_mxfp8`, `candidate`), use identical dataset,
|
| 109 |
+
prompt, profile, max tokens, and preprocessing settings. Results are written to:
|
| 110 |
+
|
| 111 |
+
```text
|
| 112 |
+
artifacts/<run_id>/
|
| 113 |
+
provenance.json
|
| 114 |
+
sensitivity_results.json
|
| 115 |
+
calibration_results.json
|
| 116 |
+
generated_precision_map.json
|
| 117 |
+
bf16_accuracy.json
|
| 118 |
+
reference_accuracy.json
|
| 119 |
+
candidate_accuracy.json
|
| 120 |
+
reference_performance.json
|
| 121 |
+
candidate_performance.json
|
| 122 |
+
candidate_rswa.json
|
| 123 |
+
release_manifest.json
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
### 2.6 Release gate
|
| 127 |
+
|
| 128 |
+
The release gate is a pure-data operation. It reads benchmark JSON and candidate
|
| 129 |
+
artifacts without loading a model. Every gate records:
|
| 130 |
+
|
| 131 |
+
```json
|
| 132 |
+
{
|
| 133 |
+
"name": "candidate_cer_vs_bf16",
|
| 134 |
+
"passed": true,
|
| 135 |
+
"actual": 0.012,
|
| 136 |
+
"limit": 0.01,
|
| 137 |
+
"detail": "candidate - bf16 absolute CER"
|
| 138 |
+
}
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
`release_approved` is true only when every required gate passes. Missing or
|
| 142 |
+
inconclusive values fail the gate.
|
| 143 |
+
|
| 144 |
+
### 2.7 Publisher
|
| 145 |
+
|
| 146 |
+
The publisher accepts only a candidate directory containing:
|
| 147 |
+
|
| 148 |
+
- model Safetensors and valid index when sharded;
|
| 149 |
+
- tokenizer and processor metadata;
|
| 150 |
+
- generated precision map;
|
| 151 |
+
- quantization summary;
|
| 152 |
+
- approved release manifest;
|
| 153 |
+
- README/model card and license.
|
| 154 |
+
|
| 155 |
+
Before upload it recomputes candidate hashes and compares them with the approved
|
| 156 |
+
manifest. It then creates one Hugging Face commit. Source checkpoint download or
|
| 157 |
+
fallback is forbidden in this path.
|
| 158 |
+
|
| 159 |
+
## 3. CLI
|
| 160 |
+
|
| 161 |
+
Primary orchestration command:
|
| 162 |
+
|
| 163 |
+
```bash
|
| 164 |
+
python quantization/run_pipeline.py \
|
| 165 |
+
--model-path reference/Unlimited-OCR \
|
| 166 |
+
--reference-model sahilchachra/unlimited-ocr-mxfp8-mlx \
|
| 167 |
+
--eval-dir eval_dataset \
|
| 168 |
+
--output-dir models/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8 \
|
| 169 |
+
--artifacts-dir artifacts/ocr-aware-v1 \
|
| 170 |
+
--image test_data/test_invoice.png
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
Publication command:
|
| 174 |
+
|
| 175 |
+
```bash
|
| 176 |
+
python scripts/publish_optimized_model.py \
|
| 177 |
+
--model-dir models/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8 \
|
| 178 |
+
--manifest artifacts/ocr-aware-v1/release_manifest.json \
|
| 179 |
+
--artifacts-dir artifacts/ocr-aware-v1 \
|
| 180 |
+
--repo-id AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
Useful modes:
|
| 184 |
+
|
| 185 |
+
- `--step preflight`
|
| 186 |
+
- `--step sensitivity`
|
| 187 |
+
- `--step precision-map`
|
| 188 |
+
- `--step convert`
|
| 189 |
+
- `--step validate`
|
| 190 |
+
- `--step gate`
|
| 191 |
+
- `--step publish`
|
| 192 |
+
- `--step all`
|
| 193 |
+
- `--dry-run`
|
| 194 |
+
|
| 195 |
+
## 4. Provenance schema
|
| 196 |
+
|
| 197 |
+
`provenance.json` includes:
|
| 198 |
+
|
| 199 |
+
- UTC run ID and timestamps;
|
| 200 |
+
- source and reference identifiers/revisions;
|
| 201 |
+
- file SHA-256 and byte sizes;
|
| 202 |
+
- dataset digest and category counts;
|
| 203 |
+
- Python, macOS, MLX, mlx-vlm, and huggingface-hub versions;
|
| 204 |
+
- Apple chip and unified memory;
|
| 205 |
+
- generation parameters and artifact digests;
|
| 206 |
+
- generated precision-map digest.
|
| 207 |
+
|
| 208 |
+
## 5. Model metadata
|
| 209 |
+
|
| 210 |
+
Published `config.json` must have:
|
| 211 |
+
|
| 212 |
+
- architecture `UnlimitedOCRForCausalLM`;
|
| 213 |
+
- `model_type: unlimited-ocr`;
|
| 214 |
+
- a positive sliding-window size;
|
| 215 |
+
- an MXFP8 quantization object generated by the converter.
|
| 216 |
+
- per-module entries showing 119 MXFP8 modules and one affine8 LM head.
|
| 217 |
+
|
| 218 |
+
Published `processor_config.json` must use:
|
| 219 |
+
|
| 220 |
+
- `processor_class: UnlimitedOCRHFProcessor`;
|
| 221 |
+
- `sft_format: unlimitedocr`.
|
| 222 |
+
|
| 223 |
+
## 6. Error handling
|
| 224 |
+
|
| 225 |
+
- Never delete or overwrite an existing output model directory.
|
| 226 |
+
- Each subprocess failure stops the pipeline.
|
| 227 |
+
- Missing metrics, zero samples, unmatched rules, natural short generations,
|
| 228 |
+
and unavailable Metal are explicit failures or inconclusive release blockers.
|
| 229 |
+
- Partial candidate output remains only in a named staging directory until the
|
| 230 |
+
converter cleans it up.
|
| 231 |
+
- Publication recomputes hashes immediately before creating the remote commit.
|
| 232 |
+
|
| 233 |
+
## 7. Testing
|
| 234 |
+
|
| 235 |
+
Unit tests cover:
|
| 236 |
+
|
| 237 |
+
- source rejection for already quantized models;
|
| 238 |
+
- sensitivity-result schema parsing;
|
| 239 |
+
- objective calibration selection across BF16, MXFP8, and affine8 heads;
|
| 240 |
+
- sensitive-group promotion to executable BF16 rules;
|
| 241 |
+
- specificity precedence in the precision map;
|
| 242 |
+
- output-directory lifecycle;
|
| 243 |
+
- release-gate pass, fail, missing, and inconclusive cases;
|
| 244 |
+
- hash mismatch rejection;
|
| 245 |
+
- publisher refusal without an approved manifest;
|
| 246 |
+
- publisher operation list containing candidate rather than source weights.
|
| 247 |
+
|
| 248 |
+
Integration tests mock model conversion and Hugging Face APIs. Hardware smoke
|
| 249 |
+
tests run BF16 and candidate inference on a real image. Full release validation
|
| 250 |
+
runs only on a Metal-capable Apple Silicon host.
|
| 251 |
+
|
| 252 |
+
## 8. Release verification
|
| 253 |
+
|
| 254 |
+
After publication:
|
| 255 |
+
|
| 256 |
+
1. Read remote file metadata and confirm all expected files exist.
|
| 257 |
+
2. Compare remote LFS SHA-256 with the approved manifest.
|
| 258 |
+
3. Load the exact remote revision with `mlx-vlm`.
|
| 259 |
+
4. Run deterministic OCR on the smoke image.
|
| 260 |
+
5. Record the final revision in the local release manifest and handoff report.
|
docs/adr/0001-verified-ocr-aware-quantization.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-0001: Build a Verified Checkpoint from BF16 and Publish Fail-Closed
|
| 2 |
+
|
| 3 |
+
Status: Accepted
|
| 4 |
+
Date: 2026-07-20
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
The project currently references `sahilchachra/unlimited-ocr-mxfp8-mlx` as its
|
| 9 |
+
weight source. That checkpoint is already MXFP8. Re-uploading it with different
|
| 10 |
+
configuration or pipeline files improves runtime behavior but is not a new
|
| 11 |
+
weight optimization.
|
| 12 |
+
|
| 13 |
+
The existing repository also contains sensitivity and mixed-precision scripts,
|
| 14 |
+
but measured overrides have not been generated and the legacy upload workflow
|
| 15 |
+
does not require proof that candidate weights differ from the source.
|
| 16 |
+
|
| 17 |
+
## Decision
|
| 18 |
+
|
| 19 |
+
We will:
|
| 20 |
+
|
| 21 |
+
1. Use the upstream Baidu BF16 checkpoint as the only valid source for
|
| 22 |
+
sensitivity analysis and final conversion.
|
| 23 |
+
2. Use the Sahil MXFP8 checkpoint only as a comparative reference.
|
| 24 |
+
3. Generate the precision map from measured group-level OCR deltas.
|
| 25 |
+
4. Preserve high-risk optical and routing components in BF16 by default.
|
| 26 |
+
5. Quantize eligible language-tower linear modules to block-float MXFP8 with
|
| 27 |
+
group size 32.
|
| 28 |
+
6. Select the native `unlimited-ocr` MLX backend so R-SWA uses its bounded
|
| 29 |
+
ring/sliding cache rather than a legacy DeepSeek-OCR fallback.
|
| 30 |
+
7. Generate a signed-by-content release manifest containing hashes, versions,
|
| 31 |
+
metrics, and explicit pass/fail gates.
|
| 32 |
+
8. Make publication fail-closed: no approved manifest means no upload.
|
| 33 |
+
9. Publish under a new, provenance-clear repository:
|
| 34 |
+
`AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8`.
|
| 35 |
+
|
| 36 |
+
## Sensitivity policy
|
| 37 |
+
|
| 38 |
+
A module group is retained in BF16 when any of these conditions is true:
|
| 39 |
+
|
| 40 |
+
- CER delta is greater than 0.02 absolute.
|
| 41 |
+
- Digit-CER delta is greater than 0.02 absolute.
|
| 42 |
+
- Table-score degradation is greater than 0.01 absolute.
|
| 43 |
+
- The group could not be measured reliably.
|
| 44 |
+
- It belongs to the protected optical/routing set.
|
| 45 |
+
|
| 46 |
+
Concrete exact-path overrides take precedence over wildcard MXFP8 rules.
|
| 47 |
+
|
| 48 |
+
## Alternatives considered
|
| 49 |
+
|
| 50 |
+
### Repackage the Sahil checkpoint
|
| 51 |
+
|
| 52 |
+
Rejected as the primary release path. It is useful for runtime tooling, but its
|
| 53 |
+
weights are not an AutomatosX optimization.
|
| 54 |
+
|
| 55 |
+
### Re-quantize the Sahil checkpoint
|
| 56 |
+
|
| 57 |
+
Rejected. Quantizing already quantized weights compounds error and prevents a
|
| 58 |
+
clean BF16 comparison.
|
| 59 |
+
|
| 60 |
+
### Uniform MXFP8 from BF16
|
| 61 |
+
|
| 62 |
+
Rejected as the final product because it does not use OCR sensitivity evidence.
|
| 63 |
+
It remains a useful control candidate.
|
| 64 |
+
|
| 65 |
+
### Per-layer exhaustive search
|
| 66 |
+
|
| 67 |
+
Deferred. It provides finer control but multiplies evaluation cost. Group-level
|
| 68 |
+
analysis is the first release; the data contract keeps concrete module paths so
|
| 69 |
+
a later release can promote individual layers.
|
| 70 |
+
|
| 71 |
+
### Publish first and benchmark later
|
| 72 |
+
|
| 73 |
+
Rejected. It would recreate the current ambiguity and allow unsupported claims.
|
| 74 |
+
|
| 75 |
+
## Consequences
|
| 76 |
+
|
| 77 |
+
Positive:
|
| 78 |
+
|
| 79 |
+
- The published checkpoint is demonstrably distinct and reproducible.
|
| 80 |
+
- Quality claims are tied to recorded evidence.
|
| 81 |
+
- Runtime improvements and weight improvements are clearly separated.
|
| 82 |
+
- A broken or incomplete benchmark cannot accidentally publish a model.
|
| 83 |
+
|
| 84 |
+
Negative:
|
| 85 |
+
|
| 86 |
+
- Release requires a representative evaluation dataset and Metal-capable Mac.
|
| 87 |
+
- Sensitivity analysis is compute intensive.
|
| 88 |
+
- Conservative BF16 retention may produce a larger model than uniform MXFP8.
|
| 89 |
+
- A failed R-SWA or accuracy gate intentionally delays publication.
|
| 90 |
+
|
docs/adr/0002-calibrated-affine8-lm-head.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-0002: Use Affine INT8 for the LM Head
|
| 2 |
+
|
| 3 |
+
Status: Accepted
|
| 4 |
+
Date: 2026-07-20
|
| 5 |
+
Amends: ADR-0001 for `language_model.lm_head` only
|
| 6 |
+
|
| 7 |
+
## Context
|
| 8 |
+
|
| 9 |
+
The group sensitivity run showed that the vision encoder must remain BF16, while
|
| 10 |
+
the projector and token embedding could be quantized without degrading CER,
|
| 11 |
+
digit CER, or table score. Joint validation then exposed a trade-off isolated
|
| 12 |
+
group sensitivity could not predict:
|
| 13 |
+
|
| 14 |
+
| LM-head mode | Mean CER | Digit CER | Decode tok/s | Release result |
|
| 15 |
+
|---|---:|---:|---:|---|
|
| 16 |
+
| BF16 | 0.231904 | 0.082224 | 271.1 | throughput failed |
|
| 17 |
+
| MXFP8 | 0.260170 | 0.076961 | 307.4 | CER failed |
|
| 18 |
+
| affine INT8 | 0.237384 | 0.085733 | 301.2 | all calibration gates passed |
|
| 19 |
+
|
| 20 |
+
The common BF16 baseline CER was 0.239188 and digit CER was 0.087340. The
|
| 21 |
+
Sahil-reference throughput was 312.7 tok/s. The selection procedure used the
|
| 22 |
+
pre-existing quality and 90% throughput limits; thresholds were not changed
|
| 23 |
+
after observing the results.
|
| 24 |
+
|
| 25 |
+
## Decision
|
| 26 |
+
|
| 27 |
+
Use MXFP8, group size 32, for 119 eligible modules. Use 8-bit affine
|
| 28 |
+
quantization, group size 32, for `language_model.lm_head`. Keep the vision
|
| 29 |
+
encoder and non-quantizable normalization/routing state in BF16.
|
| 30 |
+
|
| 31 |
+
`quantization/calibrate_precision.py` selects the fastest candidate that passes
|
| 32 |
+
every existing release constraint and records the decision in
|
| 33 |
+
`calibration_results.json`. The generated precision map consumes that record,
|
| 34 |
+
so the affine exception is reproducible and is not a manual release edit.
|
| 35 |
+
|
| 36 |
+
## Alternatives considered
|
| 37 |
+
|
| 38 |
+
### Keep the LM head in BF16
|
| 39 |
+
|
| 40 |
+
Rejected for this release because measured throughput was 86.7% of the Sahil
|
| 41 |
+
reference, below the approved 90% floor.
|
| 42 |
+
|
| 43 |
+
### Quantize the LM head to MXFP8
|
| 44 |
+
|
| 45 |
+
Rejected because mean CER degraded by 0.02098 absolute versus BF16, above the
|
| 46 |
+
0.01 ceiling, even though throughput passed.
|
| 47 |
+
|
| 48 |
+
### Lower the throughput or CER threshold
|
| 49 |
+
|
| 50 |
+
Rejected. Changing a gate after seeing a failing candidate would invalidate the
|
| 51 |
+
fail-closed release design.
|
| 52 |
+
|
| 53 |
+
## Consequences
|
| 54 |
+
|
| 55 |
+
- The checkpoint remains predominantly MXFP8 but is accurately described as a
|
| 56 |
+
mixed MXFP8/affine8 model.
|
| 57 |
+
- The final formal rerun reached 297.2 tok/s, 95.0% of the Sahil reference,
|
| 58 |
+
while mean CER and digit CER remained slightly better than BF16.
|
| 59 |
+
- Consumers must use a recent MLX/`mlx-vlm` version that supports per-module
|
| 60 |
+
affine and MXFP8 quantization metadata.
|
examples/batch_directory.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Example: Batch OCR processing of a directory of images."""
|
| 2 |
+
|
| 3 |
+
from unlimited_ocr import OCRPipeline
|
| 4 |
+
|
| 5 |
+
# Initialize the pipeline
|
| 6 |
+
pipeline = OCRPipeline(
|
| 7 |
+
model_path="AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8",
|
| 8 |
+
verbose=True,
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
# --- Process all images in a directory ---
|
| 12 |
+
# Supports: .jpg, .jpeg, .png, .tiff, .tif, .webp, .bmp
|
| 13 |
+
results = pipeline.run_batch(
|
| 14 |
+
"./scanned_documents/",
|
| 15 |
+
format="text",
|
| 16 |
+
show_progress=True, # Rich progress bar
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
# Results is a dict: {filename: ocr_text}
|
| 20 |
+
for filename, text in results.items():
|
| 21 |
+
print(f"\n{'='*60}")
|
| 22 |
+
print(f"FILE: {filename}")
|
| 23 |
+
print(f"{'='*60}")
|
| 24 |
+
print(text[:500]) # Print first 500 chars
|
| 25 |
+
|
| 26 |
+
# --- Save each result to an output directory ---
|
| 27 |
+
results = pipeline.run_batch(
|
| 28 |
+
"./scanned_documents/",
|
| 29 |
+
format="markdown",
|
| 30 |
+
output_dir="./ocr_results/", # Creates .md files per image
|
| 31 |
+
show_progress=True,
|
| 32 |
+
)
|
| 33 |
+
print(f"\nProcessed {len(results)} files → ./ocr_results/")
|
| 34 |
+
|
| 35 |
+
# --- With preprocessing for low-quality scans ---
|
| 36 |
+
results = pipeline.run_batch(
|
| 37 |
+
"./low_quality_scans/",
|
| 38 |
+
format="text",
|
| 39 |
+
preprocess=True, # deskew + contrast enhancement
|
| 40 |
+
output_dir="./cleaned_results/",
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
# --- JSON output with bounding boxes ---
|
| 44 |
+
results = pipeline.run_batch(
|
| 45 |
+
"./forms/",
|
| 46 |
+
format="json",
|
| 47 |
+
grounding=True, # Include bounding box coordinates
|
| 48 |
+
output_dir="./structured_results/",
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
# Clean up
|
| 52 |
+
pipeline.cleanup()
|
examples/multi_page_pdf.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Example: OCR a multi-page PDF document."""
|
| 2 |
+
|
| 3 |
+
from unlimited_ocr import OCRPipeline
|
| 4 |
+
|
| 5 |
+
# Initialize the pipeline
|
| 6 |
+
pipeline = OCRPipeline(
|
| 7 |
+
model_path="AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8",
|
| 8 |
+
verbose=True,
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
# --- Basic PDF OCR (all pages) ---
|
| 12 |
+
result = pipeline.run("your_document.pdf", format="text", dpi=300)
|
| 13 |
+
print(result)
|
| 14 |
+
|
| 15 |
+
# --- Save as Markdown with page headings ---
|
| 16 |
+
result = pipeline.run(
|
| 17 |
+
"your_document.pdf",
|
| 18 |
+
format="markdown",
|
| 19 |
+
dpi=300,
|
| 20 |
+
output_path="output.md",
|
| 21 |
+
)
|
| 22 |
+
print("Saved to output.md")
|
| 23 |
+
|
| 24 |
+
# --- JSON output with per-page structure ---
|
| 25 |
+
result = pipeline.run(
|
| 26 |
+
"your_document.pdf",
|
| 27 |
+
format="json",
|
| 28 |
+
dpi=300,
|
| 29 |
+
output_path="output.json",
|
| 30 |
+
)
|
| 31 |
+
print("Saved to output.json")
|
| 32 |
+
|
| 33 |
+
# --- With preprocessing for scanned PDFs ---
|
| 34 |
+
result = pipeline.run(
|
| 35 |
+
"scanned_document.pdf",
|
| 36 |
+
format="text",
|
| 37 |
+
preprocess=True, # deskew + contrast enhancement per page
|
| 38 |
+
dpi=300,
|
| 39 |
+
)
|
| 40 |
+
print(result)
|
| 41 |
+
|
| 42 |
+
# --- With progress tracking ---
|
| 43 |
+
def on_progress(current, total):
|
| 44 |
+
print(f" Processing page {current}/{total}...")
|
| 45 |
+
|
| 46 |
+
result = pipeline.run(
|
| 47 |
+
"long_document.pdf",
|
| 48 |
+
format="text",
|
| 49 |
+
progress_callback=on_progress,
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
# Clean up
|
| 53 |
+
pipeline.cleanup()
|
examples/single_image.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Example: OCR a single image file."""
|
| 2 |
+
|
| 3 |
+
from unlimited_ocr import OCRPipeline
|
| 4 |
+
|
| 5 |
+
# Initialize the pipeline (model loads lazily on first inference)
|
| 6 |
+
pipeline = OCRPipeline(
|
| 7 |
+
model_path="AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8",
|
| 8 |
+
verbose=True,
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
# --- Basic document OCR ---
|
| 12 |
+
result = pipeline.run("your_document.jpg", format="text")
|
| 13 |
+
print(result)
|
| 14 |
+
|
| 15 |
+
# --- Markdown output ---
|
| 16 |
+
result = pipeline.run("your_document.jpg", format="markdown", output_path="output.md")
|
| 17 |
+
print(f"Saved to output.md")
|
| 18 |
+
|
| 19 |
+
# --- With bounding boxes (grounding mode) ---
|
| 20 |
+
result = pipeline.run("your_document.jpg", format="json", grounding=True)
|
| 21 |
+
print(result)
|
| 22 |
+
|
| 23 |
+
# --- With image preprocessing (deskew + contrast enhancement) ---
|
| 24 |
+
result = pipeline.run("scanned_page.png", format="text", preprocess=True)
|
| 25 |
+
print(result)
|
| 26 |
+
|
| 27 |
+
# --- Different task types ---
|
| 28 |
+
# "document" — general document parsing (default)
|
| 29 |
+
# "markdown" — convert to markdown structure
|
| 30 |
+
# "figure" — parse figures/diagrams
|
| 31 |
+
# "free" — free-form OCR
|
| 32 |
+
result = pipeline.run("table.png", task="markdown", format="markdown")
|
| 33 |
+
print(result)
|
| 34 |
+
|
| 35 |
+
# Clean up temporary files
|
| 36 |
+
pipeline.cleanup()
|
generation_config.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"do_sample": false,
|
| 3 |
+
"temperature": 0.0,
|
| 4 |
+
"top_p": 1.0,
|
| 5 |
+
"max_new_tokens": 8192,
|
| 6 |
+
"repetition_penalty": 1.0,
|
| 7 |
+
"no_repeat_ngram_size": 35,
|
| 8 |
+
"use_cache": true,
|
| 9 |
+
"_comment": "Tested default generation parameters for Unlimited-OCR MLX. Deterministic decoding recommended for OCR accuracy.",
|
| 10 |
+
"_profiles": {
|
| 11 |
+
"accurate": {"max_new_tokens": 8192, "temperature": 0.0, "no_repeat_ngram_size": 35},
|
| 12 |
+
"fast": {"max_new_tokens": 2048, "temperature": 0.0, "no_repeat_ngram_size": 35},
|
| 13 |
+
"long-document": {"max_new_tokens": 16384, "temperature": 0.0, "repetition_penalty": 1.05, "no_repeat_ngram_size": 25},
|
| 14 |
+
"plain-text": {"max_new_tokens": 4096, "temperature": 0.0, "no_repeat_ngram_size": 35},
|
| 15 |
+
"markdown": {"max_new_tokens": 8192, "temperature": 0.0, "no_repeat_ngram_size": 35}
|
| 16 |
+
}
|
| 17 |
+
}
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:27568517565832e42daa563f6939e66a8ebb50cce0b01a24ecebb3ea6b4d0b10
|
| 3 |
+
size 3843160559
|
model.safetensors.index.json
ADDED
|
@@ -0,0 +1,759 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"metadata": {
|
| 3 |
+
"total_size": 3843066880
|
| 4 |
+
},
|
| 5 |
+
"weight_map": {
|
| 6 |
+
"image_newline": "model.safetensors",
|
| 7 |
+
"language_model.lm_head.biases": "model.safetensors",
|
| 8 |
+
"language_model.lm_head.scales": "model.safetensors",
|
| 9 |
+
"language_model.lm_head.weight": "model.safetensors",
|
| 10 |
+
"language_model.model.embed_tokens.scales": "model.safetensors",
|
| 11 |
+
"language_model.model.embed_tokens.weight": "model.safetensors",
|
| 12 |
+
"language_model.model.layers.0.input_layernorm.weight": "model.safetensors",
|
| 13 |
+
"language_model.model.layers.0.mlp.down_proj.scales": "model.safetensors",
|
| 14 |
+
"language_model.model.layers.0.mlp.down_proj.weight": "model.safetensors",
|
| 15 |
+
"language_model.model.layers.0.mlp.gate_proj.scales": "model.safetensors",
|
| 16 |
+
"language_model.model.layers.0.mlp.gate_proj.weight": "model.safetensors",
|
| 17 |
+
"language_model.model.layers.0.mlp.up_proj.scales": "model.safetensors",
|
| 18 |
+
"language_model.model.layers.0.mlp.up_proj.weight": "model.safetensors",
|
| 19 |
+
"language_model.model.layers.0.post_attention_layernorm.weight": "model.safetensors",
|
| 20 |
+
"language_model.model.layers.0.self_attn.k_proj.scales": "model.safetensors",
|
| 21 |
+
"language_model.model.layers.0.self_attn.k_proj.weight": "model.safetensors",
|
| 22 |
+
"language_model.model.layers.0.self_attn.o_proj.scales": "model.safetensors",
|
| 23 |
+
"language_model.model.layers.0.self_attn.o_proj.weight": "model.safetensors",
|
| 24 |
+
"language_model.model.layers.0.self_attn.q_proj.scales": "model.safetensors",
|
| 25 |
+
"language_model.model.layers.0.self_attn.q_proj.weight": "model.safetensors",
|
| 26 |
+
"language_model.model.layers.0.self_attn.v_proj.scales": "model.safetensors",
|
| 27 |
+
"language_model.model.layers.0.self_attn.v_proj.weight": "model.safetensors",
|
| 28 |
+
"language_model.model.layers.1.input_layernorm.weight": "model.safetensors",
|
| 29 |
+
"language_model.model.layers.1.mlp.gate.weight": "model.safetensors",
|
| 30 |
+
"language_model.model.layers.1.mlp.shared_experts.down_proj.scales": "model.safetensors",
|
| 31 |
+
"language_model.model.layers.1.mlp.shared_experts.down_proj.weight": "model.safetensors",
|
| 32 |
+
"language_model.model.layers.1.mlp.shared_experts.gate_proj.scales": "model.safetensors",
|
| 33 |
+
"language_model.model.layers.1.mlp.shared_experts.gate_proj.weight": "model.safetensors",
|
| 34 |
+
"language_model.model.layers.1.mlp.shared_experts.up_proj.scales": "model.safetensors",
|
| 35 |
+
"language_model.model.layers.1.mlp.shared_experts.up_proj.weight": "model.safetensors",
|
| 36 |
+
"language_model.model.layers.1.mlp.switch_mlp.down_proj.scales": "model.safetensors",
|
| 37 |
+
"language_model.model.layers.1.mlp.switch_mlp.down_proj.weight": "model.safetensors",
|
| 38 |
+
"language_model.model.layers.1.mlp.switch_mlp.gate_proj.scales": "model.safetensors",
|
| 39 |
+
"language_model.model.layers.1.mlp.switch_mlp.gate_proj.weight": "model.safetensors",
|
| 40 |
+
"language_model.model.layers.1.mlp.switch_mlp.up_proj.scales": "model.safetensors",
|
| 41 |
+
"language_model.model.layers.1.mlp.switch_mlp.up_proj.weight": "model.safetensors",
|
| 42 |
+
"language_model.model.layers.1.post_attention_layernorm.weight": "model.safetensors",
|
| 43 |
+
"language_model.model.layers.1.self_attn.k_proj.scales": "model.safetensors",
|
| 44 |
+
"language_model.model.layers.1.self_attn.k_proj.weight": "model.safetensors",
|
| 45 |
+
"language_model.model.layers.1.self_attn.o_proj.scales": "model.safetensors",
|
| 46 |
+
"language_model.model.layers.1.self_attn.o_proj.weight": "model.safetensors",
|
| 47 |
+
"language_model.model.layers.1.self_attn.q_proj.scales": "model.safetensors",
|
| 48 |
+
"language_model.model.layers.1.self_attn.q_proj.weight": "model.safetensors",
|
| 49 |
+
"language_model.model.layers.1.self_attn.v_proj.scales": "model.safetensors",
|
| 50 |
+
"language_model.model.layers.1.self_attn.v_proj.weight": "model.safetensors",
|
| 51 |
+
"language_model.model.layers.10.input_layernorm.weight": "model.safetensors",
|
| 52 |
+
"language_model.model.layers.10.mlp.gate.weight": "model.safetensors",
|
| 53 |
+
"language_model.model.layers.10.mlp.shared_experts.down_proj.scales": "model.safetensors",
|
| 54 |
+
"language_model.model.layers.10.mlp.shared_experts.down_proj.weight": "model.safetensors",
|
| 55 |
+
"language_model.model.layers.10.mlp.shared_experts.gate_proj.scales": "model.safetensors",
|
| 56 |
+
"language_model.model.layers.10.mlp.shared_experts.gate_proj.weight": "model.safetensors",
|
| 57 |
+
"language_model.model.layers.10.mlp.shared_experts.up_proj.scales": "model.safetensors",
|
| 58 |
+
"language_model.model.layers.10.mlp.shared_experts.up_proj.weight": "model.safetensors",
|
| 59 |
+
"language_model.model.layers.10.mlp.switch_mlp.down_proj.scales": "model.safetensors",
|
| 60 |
+
"language_model.model.layers.10.mlp.switch_mlp.down_proj.weight": "model.safetensors",
|
| 61 |
+
"language_model.model.layers.10.mlp.switch_mlp.gate_proj.scales": "model.safetensors",
|
| 62 |
+
"language_model.model.layers.10.mlp.switch_mlp.gate_proj.weight": "model.safetensors",
|
| 63 |
+
"language_model.model.layers.10.mlp.switch_mlp.up_proj.scales": "model.safetensors",
|
| 64 |
+
"language_model.model.layers.10.mlp.switch_mlp.up_proj.weight": "model.safetensors",
|
| 65 |
+
"language_model.model.layers.10.post_attention_layernorm.weight": "model.safetensors",
|
| 66 |
+
"language_model.model.layers.10.self_attn.k_proj.scales": "model.safetensors",
|
| 67 |
+
"language_model.model.layers.10.self_attn.k_proj.weight": "model.safetensors",
|
| 68 |
+
"language_model.model.layers.10.self_attn.o_proj.scales": "model.safetensors",
|
| 69 |
+
"language_model.model.layers.10.self_attn.o_proj.weight": "model.safetensors",
|
| 70 |
+
"language_model.model.layers.10.self_attn.q_proj.scales": "model.safetensors",
|
| 71 |
+
"language_model.model.layers.10.self_attn.q_proj.weight": "model.safetensors",
|
| 72 |
+
"language_model.model.layers.10.self_attn.v_proj.scales": "model.safetensors",
|
| 73 |
+
"language_model.model.layers.10.self_attn.v_proj.weight": "model.safetensors",
|
| 74 |
+
"language_model.model.layers.11.input_layernorm.weight": "model.safetensors",
|
| 75 |
+
"language_model.model.layers.11.mlp.gate.weight": "model.safetensors",
|
| 76 |
+
"language_model.model.layers.11.mlp.shared_experts.down_proj.scales": "model.safetensors",
|
| 77 |
+
"language_model.model.layers.11.mlp.shared_experts.down_proj.weight": "model.safetensors",
|
| 78 |
+
"language_model.model.layers.11.mlp.shared_experts.gate_proj.scales": "model.safetensors",
|
| 79 |
+
"language_model.model.layers.11.mlp.shared_experts.gate_proj.weight": "model.safetensors",
|
| 80 |
+
"language_model.model.layers.11.mlp.shared_experts.up_proj.scales": "model.safetensors",
|
| 81 |
+
"language_model.model.layers.11.mlp.shared_experts.up_proj.weight": "model.safetensors",
|
| 82 |
+
"language_model.model.layers.11.mlp.switch_mlp.down_proj.scales": "model.safetensors",
|
| 83 |
+
"language_model.model.layers.11.mlp.switch_mlp.down_proj.weight": "model.safetensors",
|
| 84 |
+
"language_model.model.layers.11.mlp.switch_mlp.gate_proj.scales": "model.safetensors",
|
| 85 |
+
"language_model.model.layers.11.mlp.switch_mlp.gate_proj.weight": "model.safetensors",
|
| 86 |
+
"language_model.model.layers.11.mlp.switch_mlp.up_proj.scales": "model.safetensors",
|
| 87 |
+
"language_model.model.layers.11.mlp.switch_mlp.up_proj.weight": "model.safetensors",
|
| 88 |
+
"language_model.model.layers.11.post_attention_layernorm.weight": "model.safetensors",
|
| 89 |
+
"language_model.model.layers.11.self_attn.k_proj.scales": "model.safetensors",
|
| 90 |
+
"language_model.model.layers.11.self_attn.k_proj.weight": "model.safetensors",
|
| 91 |
+
"language_model.model.layers.11.self_attn.o_proj.scales": "model.safetensors",
|
| 92 |
+
"language_model.model.layers.11.self_attn.o_proj.weight": "model.safetensors",
|
| 93 |
+
"language_model.model.layers.11.self_attn.q_proj.scales": "model.safetensors",
|
| 94 |
+
"language_model.model.layers.11.self_attn.q_proj.weight": "model.safetensors",
|
| 95 |
+
"language_model.model.layers.11.self_attn.v_proj.scales": "model.safetensors",
|
| 96 |
+
"language_model.model.layers.11.self_attn.v_proj.weight": "model.safetensors",
|
| 97 |
+
"language_model.model.layers.2.input_layernorm.weight": "model.safetensors",
|
| 98 |
+
"language_model.model.layers.2.mlp.gate.weight": "model.safetensors",
|
| 99 |
+
"language_model.model.layers.2.mlp.shared_experts.down_proj.scales": "model.safetensors",
|
| 100 |
+
"language_model.model.layers.2.mlp.shared_experts.down_proj.weight": "model.safetensors",
|
| 101 |
+
"language_model.model.layers.2.mlp.shared_experts.gate_proj.scales": "model.safetensors",
|
| 102 |
+
"language_model.model.layers.2.mlp.shared_experts.gate_proj.weight": "model.safetensors",
|
| 103 |
+
"language_model.model.layers.2.mlp.shared_experts.up_proj.scales": "model.safetensors",
|
| 104 |
+
"language_model.model.layers.2.mlp.shared_experts.up_proj.weight": "model.safetensors",
|
| 105 |
+
"language_model.model.layers.2.mlp.switch_mlp.down_proj.scales": "model.safetensors",
|
| 106 |
+
"language_model.model.layers.2.mlp.switch_mlp.down_proj.weight": "model.safetensors",
|
| 107 |
+
"language_model.model.layers.2.mlp.switch_mlp.gate_proj.scales": "model.safetensors",
|
| 108 |
+
"language_model.model.layers.2.mlp.switch_mlp.gate_proj.weight": "model.safetensors",
|
| 109 |
+
"language_model.model.layers.2.mlp.switch_mlp.up_proj.scales": "model.safetensors",
|
| 110 |
+
"language_model.model.layers.2.mlp.switch_mlp.up_proj.weight": "model.safetensors",
|
| 111 |
+
"language_model.model.layers.2.post_attention_layernorm.weight": "model.safetensors",
|
| 112 |
+
"language_model.model.layers.2.self_attn.k_proj.scales": "model.safetensors",
|
| 113 |
+
"language_model.model.layers.2.self_attn.k_proj.weight": "model.safetensors",
|
| 114 |
+
"language_model.model.layers.2.self_attn.o_proj.scales": "model.safetensors",
|
| 115 |
+
"language_model.model.layers.2.self_attn.o_proj.weight": "model.safetensors",
|
| 116 |
+
"language_model.model.layers.2.self_attn.q_proj.scales": "model.safetensors",
|
| 117 |
+
"language_model.model.layers.2.self_attn.q_proj.weight": "model.safetensors",
|
| 118 |
+
"language_model.model.layers.2.self_attn.v_proj.scales": "model.safetensors",
|
| 119 |
+
"language_model.model.layers.2.self_attn.v_proj.weight": "model.safetensors",
|
| 120 |
+
"language_model.model.layers.3.input_layernorm.weight": "model.safetensors",
|
| 121 |
+
"language_model.model.layers.3.mlp.gate.weight": "model.safetensors",
|
| 122 |
+
"language_model.model.layers.3.mlp.shared_experts.down_proj.scales": "model.safetensors",
|
| 123 |
+
"language_model.model.layers.3.mlp.shared_experts.down_proj.weight": "model.safetensors",
|
| 124 |
+
"language_model.model.layers.3.mlp.shared_experts.gate_proj.scales": "model.safetensors",
|
| 125 |
+
"language_model.model.layers.3.mlp.shared_experts.gate_proj.weight": "model.safetensors",
|
| 126 |
+
"language_model.model.layers.3.mlp.shared_experts.up_proj.scales": "model.safetensors",
|
| 127 |
+
"language_model.model.layers.3.mlp.shared_experts.up_proj.weight": "model.safetensors",
|
| 128 |
+
"language_model.model.layers.3.mlp.switch_mlp.down_proj.scales": "model.safetensors",
|
| 129 |
+
"language_model.model.layers.3.mlp.switch_mlp.down_proj.weight": "model.safetensors",
|
| 130 |
+
"language_model.model.layers.3.mlp.switch_mlp.gate_proj.scales": "model.safetensors",
|
| 131 |
+
"language_model.model.layers.3.mlp.switch_mlp.gate_proj.weight": "model.safetensors",
|
| 132 |
+
"language_model.model.layers.3.mlp.switch_mlp.up_proj.scales": "model.safetensors",
|
| 133 |
+
"language_model.model.layers.3.mlp.switch_mlp.up_proj.weight": "model.safetensors",
|
| 134 |
+
"language_model.model.layers.3.post_attention_layernorm.weight": "model.safetensors",
|
| 135 |
+
"language_model.model.layers.3.self_attn.k_proj.scales": "model.safetensors",
|
| 136 |
+
"language_model.model.layers.3.self_attn.k_proj.weight": "model.safetensors",
|
| 137 |
+
"language_model.model.layers.3.self_attn.o_proj.scales": "model.safetensors",
|
| 138 |
+
"language_model.model.layers.3.self_attn.o_proj.weight": "model.safetensors",
|
| 139 |
+
"language_model.model.layers.3.self_attn.q_proj.scales": "model.safetensors",
|
| 140 |
+
"language_model.model.layers.3.self_attn.q_proj.weight": "model.safetensors",
|
| 141 |
+
"language_model.model.layers.3.self_attn.v_proj.scales": "model.safetensors",
|
| 142 |
+
"language_model.model.layers.3.self_attn.v_proj.weight": "model.safetensors",
|
| 143 |
+
"language_model.model.layers.4.input_layernorm.weight": "model.safetensors",
|
| 144 |
+
"language_model.model.layers.4.mlp.gate.weight": "model.safetensors",
|
| 145 |
+
"language_model.model.layers.4.mlp.shared_experts.down_proj.scales": "model.safetensors",
|
| 146 |
+
"language_model.model.layers.4.mlp.shared_experts.down_proj.weight": "model.safetensors",
|
| 147 |
+
"language_model.model.layers.4.mlp.shared_experts.gate_proj.scales": "model.safetensors",
|
| 148 |
+
"language_model.model.layers.4.mlp.shared_experts.gate_proj.weight": "model.safetensors",
|
| 149 |
+
"language_model.model.layers.4.mlp.shared_experts.up_proj.scales": "model.safetensors",
|
| 150 |
+
"language_model.model.layers.4.mlp.shared_experts.up_proj.weight": "model.safetensors",
|
| 151 |
+
"language_model.model.layers.4.mlp.switch_mlp.down_proj.scales": "model.safetensors",
|
| 152 |
+
"language_model.model.layers.4.mlp.switch_mlp.down_proj.weight": "model.safetensors",
|
| 153 |
+
"language_model.model.layers.4.mlp.switch_mlp.gate_proj.scales": "model.safetensors",
|
| 154 |
+
"language_model.model.layers.4.mlp.switch_mlp.gate_proj.weight": "model.safetensors",
|
| 155 |
+
"language_model.model.layers.4.mlp.switch_mlp.up_proj.scales": "model.safetensors",
|
| 156 |
+
"language_model.model.layers.4.mlp.switch_mlp.up_proj.weight": "model.safetensors",
|
| 157 |
+
"language_model.model.layers.4.post_attention_layernorm.weight": "model.safetensors",
|
| 158 |
+
"language_model.model.layers.4.self_attn.k_proj.scales": "model.safetensors",
|
| 159 |
+
"language_model.model.layers.4.self_attn.k_proj.weight": "model.safetensors",
|
| 160 |
+
"language_model.model.layers.4.self_attn.o_proj.scales": "model.safetensors",
|
| 161 |
+
"language_model.model.layers.4.self_attn.o_proj.weight": "model.safetensors",
|
| 162 |
+
"language_model.model.layers.4.self_attn.q_proj.scales": "model.safetensors",
|
| 163 |
+
"language_model.model.layers.4.self_attn.q_proj.weight": "model.safetensors",
|
| 164 |
+
"language_model.model.layers.4.self_attn.v_proj.scales": "model.safetensors",
|
| 165 |
+
"language_model.model.layers.4.self_attn.v_proj.weight": "model.safetensors",
|
| 166 |
+
"language_model.model.layers.5.input_layernorm.weight": "model.safetensors",
|
| 167 |
+
"language_model.model.layers.5.mlp.gate.weight": "model.safetensors",
|
| 168 |
+
"language_model.model.layers.5.mlp.shared_experts.down_proj.scales": "model.safetensors",
|
| 169 |
+
"language_model.model.layers.5.mlp.shared_experts.down_proj.weight": "model.safetensors",
|
| 170 |
+
"language_model.model.layers.5.mlp.shared_experts.gate_proj.scales": "model.safetensors",
|
| 171 |
+
"language_model.model.layers.5.mlp.shared_experts.gate_proj.weight": "model.safetensors",
|
| 172 |
+
"language_model.model.layers.5.mlp.shared_experts.up_proj.scales": "model.safetensors",
|
| 173 |
+
"language_model.model.layers.5.mlp.shared_experts.up_proj.weight": "model.safetensors",
|
| 174 |
+
"language_model.model.layers.5.mlp.switch_mlp.down_proj.scales": "model.safetensors",
|
| 175 |
+
"language_model.model.layers.5.mlp.switch_mlp.down_proj.weight": "model.safetensors",
|
| 176 |
+
"language_model.model.layers.5.mlp.switch_mlp.gate_proj.scales": "model.safetensors",
|
| 177 |
+
"language_model.model.layers.5.mlp.switch_mlp.gate_proj.weight": "model.safetensors",
|
| 178 |
+
"language_model.model.layers.5.mlp.switch_mlp.up_proj.scales": "model.safetensors",
|
| 179 |
+
"language_model.model.layers.5.mlp.switch_mlp.up_proj.weight": "model.safetensors",
|
| 180 |
+
"language_model.model.layers.5.post_attention_layernorm.weight": "model.safetensors",
|
| 181 |
+
"language_model.model.layers.5.self_attn.k_proj.scales": "model.safetensors",
|
| 182 |
+
"language_model.model.layers.5.self_attn.k_proj.weight": "model.safetensors",
|
| 183 |
+
"language_model.model.layers.5.self_attn.o_proj.scales": "model.safetensors",
|
| 184 |
+
"language_model.model.layers.5.self_attn.o_proj.weight": "model.safetensors",
|
| 185 |
+
"language_model.model.layers.5.self_attn.q_proj.scales": "model.safetensors",
|
| 186 |
+
"language_model.model.layers.5.self_attn.q_proj.weight": "model.safetensors",
|
| 187 |
+
"language_model.model.layers.5.self_attn.v_proj.scales": "model.safetensors",
|
| 188 |
+
"language_model.model.layers.5.self_attn.v_proj.weight": "model.safetensors",
|
| 189 |
+
"language_model.model.layers.6.input_layernorm.weight": "model.safetensors",
|
| 190 |
+
"language_model.model.layers.6.mlp.gate.weight": "model.safetensors",
|
| 191 |
+
"language_model.model.layers.6.mlp.shared_experts.down_proj.scales": "model.safetensors",
|
| 192 |
+
"language_model.model.layers.6.mlp.shared_experts.down_proj.weight": "model.safetensors",
|
| 193 |
+
"language_model.model.layers.6.mlp.shared_experts.gate_proj.scales": "model.safetensors",
|
| 194 |
+
"language_model.model.layers.6.mlp.shared_experts.gate_proj.weight": "model.safetensors",
|
| 195 |
+
"language_model.model.layers.6.mlp.shared_experts.up_proj.scales": "model.safetensors",
|
| 196 |
+
"language_model.model.layers.6.mlp.shared_experts.up_proj.weight": "model.safetensors",
|
| 197 |
+
"language_model.model.layers.6.mlp.switch_mlp.down_proj.scales": "model.safetensors",
|
| 198 |
+
"language_model.model.layers.6.mlp.switch_mlp.down_proj.weight": "model.safetensors",
|
| 199 |
+
"language_model.model.layers.6.mlp.switch_mlp.gate_proj.scales": "model.safetensors",
|
| 200 |
+
"language_model.model.layers.6.mlp.switch_mlp.gate_proj.weight": "model.safetensors",
|
| 201 |
+
"language_model.model.layers.6.mlp.switch_mlp.up_proj.scales": "model.safetensors",
|
| 202 |
+
"language_model.model.layers.6.mlp.switch_mlp.up_proj.weight": "model.safetensors",
|
| 203 |
+
"language_model.model.layers.6.post_attention_layernorm.weight": "model.safetensors",
|
| 204 |
+
"language_model.model.layers.6.self_attn.k_proj.scales": "model.safetensors",
|
| 205 |
+
"language_model.model.layers.6.self_attn.k_proj.weight": "model.safetensors",
|
| 206 |
+
"language_model.model.layers.6.self_attn.o_proj.scales": "model.safetensors",
|
| 207 |
+
"language_model.model.layers.6.self_attn.o_proj.weight": "model.safetensors",
|
| 208 |
+
"language_model.model.layers.6.self_attn.q_proj.scales": "model.safetensors",
|
| 209 |
+
"language_model.model.layers.6.self_attn.q_proj.weight": "model.safetensors",
|
| 210 |
+
"language_model.model.layers.6.self_attn.v_proj.scales": "model.safetensors",
|
| 211 |
+
"language_model.model.layers.6.self_attn.v_proj.weight": "model.safetensors",
|
| 212 |
+
"language_model.model.layers.7.input_layernorm.weight": "model.safetensors",
|
| 213 |
+
"language_model.model.layers.7.mlp.gate.weight": "model.safetensors",
|
| 214 |
+
"language_model.model.layers.7.mlp.shared_experts.down_proj.scales": "model.safetensors",
|
| 215 |
+
"language_model.model.layers.7.mlp.shared_experts.down_proj.weight": "model.safetensors",
|
| 216 |
+
"language_model.model.layers.7.mlp.shared_experts.gate_proj.scales": "model.safetensors",
|
| 217 |
+
"language_model.model.layers.7.mlp.shared_experts.gate_proj.weight": "model.safetensors",
|
| 218 |
+
"language_model.model.layers.7.mlp.shared_experts.up_proj.scales": "model.safetensors",
|
| 219 |
+
"language_model.model.layers.7.mlp.shared_experts.up_proj.weight": "model.safetensors",
|
| 220 |
+
"language_model.model.layers.7.mlp.switch_mlp.down_proj.scales": "model.safetensors",
|
| 221 |
+
"language_model.model.layers.7.mlp.switch_mlp.down_proj.weight": "model.safetensors",
|
| 222 |
+
"language_model.model.layers.7.mlp.switch_mlp.gate_proj.scales": "model.safetensors",
|
| 223 |
+
"language_model.model.layers.7.mlp.switch_mlp.gate_proj.weight": "model.safetensors",
|
| 224 |
+
"language_model.model.layers.7.mlp.switch_mlp.up_proj.scales": "model.safetensors",
|
| 225 |
+
"language_model.model.layers.7.mlp.switch_mlp.up_proj.weight": "model.safetensors",
|
| 226 |
+
"language_model.model.layers.7.post_attention_layernorm.weight": "model.safetensors",
|
| 227 |
+
"language_model.model.layers.7.self_attn.k_proj.scales": "model.safetensors",
|
| 228 |
+
"language_model.model.layers.7.self_attn.k_proj.weight": "model.safetensors",
|
| 229 |
+
"language_model.model.layers.7.self_attn.o_proj.scales": "model.safetensors",
|
| 230 |
+
"language_model.model.layers.7.self_attn.o_proj.weight": "model.safetensors",
|
| 231 |
+
"language_model.model.layers.7.self_attn.q_proj.scales": "model.safetensors",
|
| 232 |
+
"language_model.model.layers.7.self_attn.q_proj.weight": "model.safetensors",
|
| 233 |
+
"language_model.model.layers.7.self_attn.v_proj.scales": "model.safetensors",
|
| 234 |
+
"language_model.model.layers.7.self_attn.v_proj.weight": "model.safetensors",
|
| 235 |
+
"language_model.model.layers.8.input_layernorm.weight": "model.safetensors",
|
| 236 |
+
"language_model.model.layers.8.mlp.gate.weight": "model.safetensors",
|
| 237 |
+
"language_model.model.layers.8.mlp.shared_experts.down_proj.scales": "model.safetensors",
|
| 238 |
+
"language_model.model.layers.8.mlp.shared_experts.down_proj.weight": "model.safetensors",
|
| 239 |
+
"language_model.model.layers.8.mlp.shared_experts.gate_proj.scales": "model.safetensors",
|
| 240 |
+
"language_model.model.layers.8.mlp.shared_experts.gate_proj.weight": "model.safetensors",
|
| 241 |
+
"language_model.model.layers.8.mlp.shared_experts.up_proj.scales": "model.safetensors",
|
| 242 |
+
"language_model.model.layers.8.mlp.shared_experts.up_proj.weight": "model.safetensors",
|
| 243 |
+
"language_model.model.layers.8.mlp.switch_mlp.down_proj.scales": "model.safetensors",
|
| 244 |
+
"language_model.model.layers.8.mlp.switch_mlp.down_proj.weight": "model.safetensors",
|
| 245 |
+
"language_model.model.layers.8.mlp.switch_mlp.gate_proj.scales": "model.safetensors",
|
| 246 |
+
"language_model.model.layers.8.mlp.switch_mlp.gate_proj.weight": "model.safetensors",
|
| 247 |
+
"language_model.model.layers.8.mlp.switch_mlp.up_proj.scales": "model.safetensors",
|
| 248 |
+
"language_model.model.layers.8.mlp.switch_mlp.up_proj.weight": "model.safetensors",
|
| 249 |
+
"language_model.model.layers.8.post_attention_layernorm.weight": "model.safetensors",
|
| 250 |
+
"language_model.model.layers.8.self_attn.k_proj.scales": "model.safetensors",
|
| 251 |
+
"language_model.model.layers.8.self_attn.k_proj.weight": "model.safetensors",
|
| 252 |
+
"language_model.model.layers.8.self_attn.o_proj.scales": "model.safetensors",
|
| 253 |
+
"language_model.model.layers.8.self_attn.o_proj.weight": "model.safetensors",
|
| 254 |
+
"language_model.model.layers.8.self_attn.q_proj.scales": "model.safetensors",
|
| 255 |
+
"language_model.model.layers.8.self_attn.q_proj.weight": "model.safetensors",
|
| 256 |
+
"language_model.model.layers.8.self_attn.v_proj.scales": "model.safetensors",
|
| 257 |
+
"language_model.model.layers.8.self_attn.v_proj.weight": "model.safetensors",
|
| 258 |
+
"language_model.model.layers.9.input_layernorm.weight": "model.safetensors",
|
| 259 |
+
"language_model.model.layers.9.mlp.gate.weight": "model.safetensors",
|
| 260 |
+
"language_model.model.layers.9.mlp.shared_experts.down_proj.scales": "model.safetensors",
|
| 261 |
+
"language_model.model.layers.9.mlp.shared_experts.down_proj.weight": "model.safetensors",
|
| 262 |
+
"language_model.model.layers.9.mlp.shared_experts.gate_proj.scales": "model.safetensors",
|
| 263 |
+
"language_model.model.layers.9.mlp.shared_experts.gate_proj.weight": "model.safetensors",
|
| 264 |
+
"language_model.model.layers.9.mlp.shared_experts.up_proj.scales": "model.safetensors",
|
| 265 |
+
"language_model.model.layers.9.mlp.shared_experts.up_proj.weight": "model.safetensors",
|
| 266 |
+
"language_model.model.layers.9.mlp.switch_mlp.down_proj.scales": "model.safetensors",
|
| 267 |
+
"language_model.model.layers.9.mlp.switch_mlp.down_proj.weight": "model.safetensors",
|
| 268 |
+
"language_model.model.layers.9.mlp.switch_mlp.gate_proj.scales": "model.safetensors",
|
| 269 |
+
"language_model.model.layers.9.mlp.switch_mlp.gate_proj.weight": "model.safetensors",
|
| 270 |
+
"language_model.model.layers.9.mlp.switch_mlp.up_proj.scales": "model.safetensors",
|
| 271 |
+
"language_model.model.layers.9.mlp.switch_mlp.up_proj.weight": "model.safetensors",
|
| 272 |
+
"language_model.model.layers.9.post_attention_layernorm.weight": "model.safetensors",
|
| 273 |
+
"language_model.model.layers.9.self_attn.k_proj.scales": "model.safetensors",
|
| 274 |
+
"language_model.model.layers.9.self_attn.k_proj.weight": "model.safetensors",
|
| 275 |
+
"language_model.model.layers.9.self_attn.o_proj.scales": "model.safetensors",
|
| 276 |
+
"language_model.model.layers.9.self_attn.o_proj.weight": "model.safetensors",
|
| 277 |
+
"language_model.model.layers.9.self_attn.q_proj.scales": "model.safetensors",
|
| 278 |
+
"language_model.model.layers.9.self_attn.q_proj.weight": "model.safetensors",
|
| 279 |
+
"language_model.model.layers.9.self_attn.v_proj.scales": "model.safetensors",
|
| 280 |
+
"language_model.model.layers.9.self_attn.v_proj.weight": "model.safetensors",
|
| 281 |
+
"language_model.model.norm.weight": "model.safetensors",
|
| 282 |
+
"projector.layers.bias": "model.safetensors",
|
| 283 |
+
"projector.layers.scales": "model.safetensors",
|
| 284 |
+
"projector.layers.weight": "model.safetensors",
|
| 285 |
+
"sam_model.blocks.0.attn.proj.bias": "model.safetensors",
|
| 286 |
+
"sam_model.blocks.0.attn.proj.weight": "model.safetensors",
|
| 287 |
+
"sam_model.blocks.0.attn.qkv.bias": "model.safetensors",
|
| 288 |
+
"sam_model.blocks.0.attn.qkv.weight": "model.safetensors",
|
| 289 |
+
"sam_model.blocks.0.attn.rel_pos_h": "model.safetensors",
|
| 290 |
+
"sam_model.blocks.0.attn.rel_pos_w": "model.safetensors",
|
| 291 |
+
"sam_model.blocks.0.mlp.lin1.bias": "model.safetensors",
|
| 292 |
+
"sam_model.blocks.0.mlp.lin1.weight": "model.safetensors",
|
| 293 |
+
"sam_model.blocks.0.mlp.lin2.bias": "model.safetensors",
|
| 294 |
+
"sam_model.blocks.0.mlp.lin2.weight": "model.safetensors",
|
| 295 |
+
"sam_model.blocks.0.norm1.bias": "model.safetensors",
|
| 296 |
+
"sam_model.blocks.0.norm1.weight": "model.safetensors",
|
| 297 |
+
"sam_model.blocks.0.norm2.bias": "model.safetensors",
|
| 298 |
+
"sam_model.blocks.0.norm2.weight": "model.safetensors",
|
| 299 |
+
"sam_model.blocks.1.attn.proj.bias": "model.safetensors",
|
| 300 |
+
"sam_model.blocks.1.attn.proj.weight": "model.safetensors",
|
| 301 |
+
"sam_model.blocks.1.attn.qkv.bias": "model.safetensors",
|
| 302 |
+
"sam_model.blocks.1.attn.qkv.weight": "model.safetensors",
|
| 303 |
+
"sam_model.blocks.1.attn.rel_pos_h": "model.safetensors",
|
| 304 |
+
"sam_model.blocks.1.attn.rel_pos_w": "model.safetensors",
|
| 305 |
+
"sam_model.blocks.1.mlp.lin1.bias": "model.safetensors",
|
| 306 |
+
"sam_model.blocks.1.mlp.lin1.weight": "model.safetensors",
|
| 307 |
+
"sam_model.blocks.1.mlp.lin2.bias": "model.safetensors",
|
| 308 |
+
"sam_model.blocks.1.mlp.lin2.weight": "model.safetensors",
|
| 309 |
+
"sam_model.blocks.1.norm1.bias": "model.safetensors",
|
| 310 |
+
"sam_model.blocks.1.norm1.weight": "model.safetensors",
|
| 311 |
+
"sam_model.blocks.1.norm2.bias": "model.safetensors",
|
| 312 |
+
"sam_model.blocks.1.norm2.weight": "model.safetensors",
|
| 313 |
+
"sam_model.blocks.10.attn.proj.bias": "model.safetensors",
|
| 314 |
+
"sam_model.blocks.10.attn.proj.weight": "model.safetensors",
|
| 315 |
+
"sam_model.blocks.10.attn.qkv.bias": "model.safetensors",
|
| 316 |
+
"sam_model.blocks.10.attn.qkv.weight": "model.safetensors",
|
| 317 |
+
"sam_model.blocks.10.attn.rel_pos_h": "model.safetensors",
|
| 318 |
+
"sam_model.blocks.10.attn.rel_pos_w": "model.safetensors",
|
| 319 |
+
"sam_model.blocks.10.mlp.lin1.bias": "model.safetensors",
|
| 320 |
+
"sam_model.blocks.10.mlp.lin1.weight": "model.safetensors",
|
| 321 |
+
"sam_model.blocks.10.mlp.lin2.bias": "model.safetensors",
|
| 322 |
+
"sam_model.blocks.10.mlp.lin2.weight": "model.safetensors",
|
| 323 |
+
"sam_model.blocks.10.norm1.bias": "model.safetensors",
|
| 324 |
+
"sam_model.blocks.10.norm1.weight": "model.safetensors",
|
| 325 |
+
"sam_model.blocks.10.norm2.bias": "model.safetensors",
|
| 326 |
+
"sam_model.blocks.10.norm2.weight": "model.safetensors",
|
| 327 |
+
"sam_model.blocks.11.attn.proj.bias": "model.safetensors",
|
| 328 |
+
"sam_model.blocks.11.attn.proj.weight": "model.safetensors",
|
| 329 |
+
"sam_model.blocks.11.attn.qkv.bias": "model.safetensors",
|
| 330 |
+
"sam_model.blocks.11.attn.qkv.weight": "model.safetensors",
|
| 331 |
+
"sam_model.blocks.11.attn.rel_pos_h": "model.safetensors",
|
| 332 |
+
"sam_model.blocks.11.attn.rel_pos_w": "model.safetensors",
|
| 333 |
+
"sam_model.blocks.11.mlp.lin1.bias": "model.safetensors",
|
| 334 |
+
"sam_model.blocks.11.mlp.lin1.weight": "model.safetensors",
|
| 335 |
+
"sam_model.blocks.11.mlp.lin2.bias": "model.safetensors",
|
| 336 |
+
"sam_model.blocks.11.mlp.lin2.weight": "model.safetensors",
|
| 337 |
+
"sam_model.blocks.11.norm1.bias": "model.safetensors",
|
| 338 |
+
"sam_model.blocks.11.norm1.weight": "model.safetensors",
|
| 339 |
+
"sam_model.blocks.11.norm2.bias": "model.safetensors",
|
| 340 |
+
"sam_model.blocks.11.norm2.weight": "model.safetensors",
|
| 341 |
+
"sam_model.blocks.2.attn.proj.bias": "model.safetensors",
|
| 342 |
+
"sam_model.blocks.2.attn.proj.weight": "model.safetensors",
|
| 343 |
+
"sam_model.blocks.2.attn.qkv.bias": "model.safetensors",
|
| 344 |
+
"sam_model.blocks.2.attn.qkv.weight": "model.safetensors",
|
| 345 |
+
"sam_model.blocks.2.attn.rel_pos_h": "model.safetensors",
|
| 346 |
+
"sam_model.blocks.2.attn.rel_pos_w": "model.safetensors",
|
| 347 |
+
"sam_model.blocks.2.mlp.lin1.bias": "model.safetensors",
|
| 348 |
+
"sam_model.blocks.2.mlp.lin1.weight": "model.safetensors",
|
| 349 |
+
"sam_model.blocks.2.mlp.lin2.bias": "model.safetensors",
|
| 350 |
+
"sam_model.blocks.2.mlp.lin2.weight": "model.safetensors",
|
| 351 |
+
"sam_model.blocks.2.norm1.bias": "model.safetensors",
|
| 352 |
+
"sam_model.blocks.2.norm1.weight": "model.safetensors",
|
| 353 |
+
"sam_model.blocks.2.norm2.bias": "model.safetensors",
|
| 354 |
+
"sam_model.blocks.2.norm2.weight": "model.safetensors",
|
| 355 |
+
"sam_model.blocks.3.attn.proj.bias": "model.safetensors",
|
| 356 |
+
"sam_model.blocks.3.attn.proj.weight": "model.safetensors",
|
| 357 |
+
"sam_model.blocks.3.attn.qkv.bias": "model.safetensors",
|
| 358 |
+
"sam_model.blocks.3.attn.qkv.weight": "model.safetensors",
|
| 359 |
+
"sam_model.blocks.3.attn.rel_pos_h": "model.safetensors",
|
| 360 |
+
"sam_model.blocks.3.attn.rel_pos_w": "model.safetensors",
|
| 361 |
+
"sam_model.blocks.3.mlp.lin1.bias": "model.safetensors",
|
| 362 |
+
"sam_model.blocks.3.mlp.lin1.weight": "model.safetensors",
|
| 363 |
+
"sam_model.blocks.3.mlp.lin2.bias": "model.safetensors",
|
| 364 |
+
"sam_model.blocks.3.mlp.lin2.weight": "model.safetensors",
|
| 365 |
+
"sam_model.blocks.3.norm1.bias": "model.safetensors",
|
| 366 |
+
"sam_model.blocks.3.norm1.weight": "model.safetensors",
|
| 367 |
+
"sam_model.blocks.3.norm2.bias": "model.safetensors",
|
| 368 |
+
"sam_model.blocks.3.norm2.weight": "model.safetensors",
|
| 369 |
+
"sam_model.blocks.4.attn.proj.bias": "model.safetensors",
|
| 370 |
+
"sam_model.blocks.4.attn.proj.weight": "model.safetensors",
|
| 371 |
+
"sam_model.blocks.4.attn.qkv.bias": "model.safetensors",
|
| 372 |
+
"sam_model.blocks.4.attn.qkv.weight": "model.safetensors",
|
| 373 |
+
"sam_model.blocks.4.attn.rel_pos_h": "model.safetensors",
|
| 374 |
+
"sam_model.blocks.4.attn.rel_pos_w": "model.safetensors",
|
| 375 |
+
"sam_model.blocks.4.mlp.lin1.bias": "model.safetensors",
|
| 376 |
+
"sam_model.blocks.4.mlp.lin1.weight": "model.safetensors",
|
| 377 |
+
"sam_model.blocks.4.mlp.lin2.bias": "model.safetensors",
|
| 378 |
+
"sam_model.blocks.4.mlp.lin2.weight": "model.safetensors",
|
| 379 |
+
"sam_model.blocks.4.norm1.bias": "model.safetensors",
|
| 380 |
+
"sam_model.blocks.4.norm1.weight": "model.safetensors",
|
| 381 |
+
"sam_model.blocks.4.norm2.bias": "model.safetensors",
|
| 382 |
+
"sam_model.blocks.4.norm2.weight": "model.safetensors",
|
| 383 |
+
"sam_model.blocks.5.attn.proj.bias": "model.safetensors",
|
| 384 |
+
"sam_model.blocks.5.attn.proj.weight": "model.safetensors",
|
| 385 |
+
"sam_model.blocks.5.attn.qkv.bias": "model.safetensors",
|
| 386 |
+
"sam_model.blocks.5.attn.qkv.weight": "model.safetensors",
|
| 387 |
+
"sam_model.blocks.5.attn.rel_pos_h": "model.safetensors",
|
| 388 |
+
"sam_model.blocks.5.attn.rel_pos_w": "model.safetensors",
|
| 389 |
+
"sam_model.blocks.5.mlp.lin1.bias": "model.safetensors",
|
| 390 |
+
"sam_model.blocks.5.mlp.lin1.weight": "model.safetensors",
|
| 391 |
+
"sam_model.blocks.5.mlp.lin2.bias": "model.safetensors",
|
| 392 |
+
"sam_model.blocks.5.mlp.lin2.weight": "model.safetensors",
|
| 393 |
+
"sam_model.blocks.5.norm1.bias": "model.safetensors",
|
| 394 |
+
"sam_model.blocks.5.norm1.weight": "model.safetensors",
|
| 395 |
+
"sam_model.blocks.5.norm2.bias": "model.safetensors",
|
| 396 |
+
"sam_model.blocks.5.norm2.weight": "model.safetensors",
|
| 397 |
+
"sam_model.blocks.6.attn.proj.bias": "model.safetensors",
|
| 398 |
+
"sam_model.blocks.6.attn.proj.weight": "model.safetensors",
|
| 399 |
+
"sam_model.blocks.6.attn.qkv.bias": "model.safetensors",
|
| 400 |
+
"sam_model.blocks.6.attn.qkv.weight": "model.safetensors",
|
| 401 |
+
"sam_model.blocks.6.attn.rel_pos_h": "model.safetensors",
|
| 402 |
+
"sam_model.blocks.6.attn.rel_pos_w": "model.safetensors",
|
| 403 |
+
"sam_model.blocks.6.mlp.lin1.bias": "model.safetensors",
|
| 404 |
+
"sam_model.blocks.6.mlp.lin1.weight": "model.safetensors",
|
| 405 |
+
"sam_model.blocks.6.mlp.lin2.bias": "model.safetensors",
|
| 406 |
+
"sam_model.blocks.6.mlp.lin2.weight": "model.safetensors",
|
| 407 |
+
"sam_model.blocks.6.norm1.bias": "model.safetensors",
|
| 408 |
+
"sam_model.blocks.6.norm1.weight": "model.safetensors",
|
| 409 |
+
"sam_model.blocks.6.norm2.bias": "model.safetensors",
|
| 410 |
+
"sam_model.blocks.6.norm2.weight": "model.safetensors",
|
| 411 |
+
"sam_model.blocks.7.attn.proj.bias": "model.safetensors",
|
| 412 |
+
"sam_model.blocks.7.attn.proj.weight": "model.safetensors",
|
| 413 |
+
"sam_model.blocks.7.attn.qkv.bias": "model.safetensors",
|
| 414 |
+
"sam_model.blocks.7.attn.qkv.weight": "model.safetensors",
|
| 415 |
+
"sam_model.blocks.7.attn.rel_pos_h": "model.safetensors",
|
| 416 |
+
"sam_model.blocks.7.attn.rel_pos_w": "model.safetensors",
|
| 417 |
+
"sam_model.blocks.7.mlp.lin1.bias": "model.safetensors",
|
| 418 |
+
"sam_model.blocks.7.mlp.lin1.weight": "model.safetensors",
|
| 419 |
+
"sam_model.blocks.7.mlp.lin2.bias": "model.safetensors",
|
| 420 |
+
"sam_model.blocks.7.mlp.lin2.weight": "model.safetensors",
|
| 421 |
+
"sam_model.blocks.7.norm1.bias": "model.safetensors",
|
| 422 |
+
"sam_model.blocks.7.norm1.weight": "model.safetensors",
|
| 423 |
+
"sam_model.blocks.7.norm2.bias": "model.safetensors",
|
| 424 |
+
"sam_model.blocks.7.norm2.weight": "model.safetensors",
|
| 425 |
+
"sam_model.blocks.8.attn.proj.bias": "model.safetensors",
|
| 426 |
+
"sam_model.blocks.8.attn.proj.weight": "model.safetensors",
|
| 427 |
+
"sam_model.blocks.8.attn.qkv.bias": "model.safetensors",
|
| 428 |
+
"sam_model.blocks.8.attn.qkv.weight": "model.safetensors",
|
| 429 |
+
"sam_model.blocks.8.attn.rel_pos_h": "model.safetensors",
|
| 430 |
+
"sam_model.blocks.8.attn.rel_pos_w": "model.safetensors",
|
| 431 |
+
"sam_model.blocks.8.mlp.lin1.bias": "model.safetensors",
|
| 432 |
+
"sam_model.blocks.8.mlp.lin1.weight": "model.safetensors",
|
| 433 |
+
"sam_model.blocks.8.mlp.lin2.bias": "model.safetensors",
|
| 434 |
+
"sam_model.blocks.8.mlp.lin2.weight": "model.safetensors",
|
| 435 |
+
"sam_model.blocks.8.norm1.bias": "model.safetensors",
|
| 436 |
+
"sam_model.blocks.8.norm1.weight": "model.safetensors",
|
| 437 |
+
"sam_model.blocks.8.norm2.bias": "model.safetensors",
|
| 438 |
+
"sam_model.blocks.8.norm2.weight": "model.safetensors",
|
| 439 |
+
"sam_model.blocks.9.attn.proj.bias": "model.safetensors",
|
| 440 |
+
"sam_model.blocks.9.attn.proj.weight": "model.safetensors",
|
| 441 |
+
"sam_model.blocks.9.attn.qkv.bias": "model.safetensors",
|
| 442 |
+
"sam_model.blocks.9.attn.qkv.weight": "model.safetensors",
|
| 443 |
+
"sam_model.blocks.9.attn.rel_pos_h": "model.safetensors",
|
| 444 |
+
"sam_model.blocks.9.attn.rel_pos_w": "model.safetensors",
|
| 445 |
+
"sam_model.blocks.9.mlp.lin1.bias": "model.safetensors",
|
| 446 |
+
"sam_model.blocks.9.mlp.lin1.weight": "model.safetensors",
|
| 447 |
+
"sam_model.blocks.9.mlp.lin2.bias": "model.safetensors",
|
| 448 |
+
"sam_model.blocks.9.mlp.lin2.weight": "model.safetensors",
|
| 449 |
+
"sam_model.blocks.9.norm1.bias": "model.safetensors",
|
| 450 |
+
"sam_model.blocks.9.norm1.weight": "model.safetensors",
|
| 451 |
+
"sam_model.blocks.9.norm2.bias": "model.safetensors",
|
| 452 |
+
"sam_model.blocks.9.norm2.weight": "model.safetensors",
|
| 453 |
+
"sam_model.neck.0.weight": "model.safetensors",
|
| 454 |
+
"sam_model.neck.1.bias": "model.safetensors",
|
| 455 |
+
"sam_model.neck.1.weight": "model.safetensors",
|
| 456 |
+
"sam_model.neck.2.weight": "model.safetensors",
|
| 457 |
+
"sam_model.neck.3.bias": "model.safetensors",
|
| 458 |
+
"sam_model.neck.3.weight": "model.safetensors",
|
| 459 |
+
"sam_model.net_2.weight": "model.safetensors",
|
| 460 |
+
"sam_model.net_3.weight": "model.safetensors",
|
| 461 |
+
"sam_model.patch_embed.proj.bias": "model.safetensors",
|
| 462 |
+
"sam_model.patch_embed.proj.weight": "model.safetensors",
|
| 463 |
+
"sam_model.pos_embed": "model.safetensors",
|
| 464 |
+
"view_separator": "model.safetensors",
|
| 465 |
+
"vision_model.embeddings.class_embedding": "model.safetensors",
|
| 466 |
+
"vision_model.embeddings.patch_embedding.weight": "model.safetensors",
|
| 467 |
+
"vision_model.embeddings.position_embedding.weight": "model.safetensors",
|
| 468 |
+
"vision_model.pre_layrnorm.bias": "model.safetensors",
|
| 469 |
+
"vision_model.pre_layrnorm.weight": "model.safetensors",
|
| 470 |
+
"vision_model.transformer.layers.0.layer_norm1.bias": "model.safetensors",
|
| 471 |
+
"vision_model.transformer.layers.0.layer_norm1.weight": "model.safetensors",
|
| 472 |
+
"vision_model.transformer.layers.0.layer_norm2.bias": "model.safetensors",
|
| 473 |
+
"vision_model.transformer.layers.0.layer_norm2.weight": "model.safetensors",
|
| 474 |
+
"vision_model.transformer.layers.0.mlp.fc1.bias": "model.safetensors",
|
| 475 |
+
"vision_model.transformer.layers.0.mlp.fc1.weight": "model.safetensors",
|
| 476 |
+
"vision_model.transformer.layers.0.mlp.fc2.bias": "model.safetensors",
|
| 477 |
+
"vision_model.transformer.layers.0.mlp.fc2.weight": "model.safetensors",
|
| 478 |
+
"vision_model.transformer.layers.0.self_attn.out_proj.bias": "model.safetensors",
|
| 479 |
+
"vision_model.transformer.layers.0.self_attn.out_proj.weight": "model.safetensors",
|
| 480 |
+
"vision_model.transformer.layers.0.self_attn.qkv_proj.bias": "model.safetensors",
|
| 481 |
+
"vision_model.transformer.layers.0.self_attn.qkv_proj.weight": "model.safetensors",
|
| 482 |
+
"vision_model.transformer.layers.1.layer_norm1.bias": "model.safetensors",
|
| 483 |
+
"vision_model.transformer.layers.1.layer_norm1.weight": "model.safetensors",
|
| 484 |
+
"vision_model.transformer.layers.1.layer_norm2.bias": "model.safetensors",
|
| 485 |
+
"vision_model.transformer.layers.1.layer_norm2.weight": "model.safetensors",
|
| 486 |
+
"vision_model.transformer.layers.1.mlp.fc1.bias": "model.safetensors",
|
| 487 |
+
"vision_model.transformer.layers.1.mlp.fc1.weight": "model.safetensors",
|
| 488 |
+
"vision_model.transformer.layers.1.mlp.fc2.bias": "model.safetensors",
|
| 489 |
+
"vision_model.transformer.layers.1.mlp.fc2.weight": "model.safetensors",
|
| 490 |
+
"vision_model.transformer.layers.1.self_attn.out_proj.bias": "model.safetensors",
|
| 491 |
+
"vision_model.transformer.layers.1.self_attn.out_proj.weight": "model.safetensors",
|
| 492 |
+
"vision_model.transformer.layers.1.self_attn.qkv_proj.bias": "model.safetensors",
|
| 493 |
+
"vision_model.transformer.layers.1.self_attn.qkv_proj.weight": "model.safetensors",
|
| 494 |
+
"vision_model.transformer.layers.10.layer_norm1.bias": "model.safetensors",
|
| 495 |
+
"vision_model.transformer.layers.10.layer_norm1.weight": "model.safetensors",
|
| 496 |
+
"vision_model.transformer.layers.10.layer_norm2.bias": "model.safetensors",
|
| 497 |
+
"vision_model.transformer.layers.10.layer_norm2.weight": "model.safetensors",
|
| 498 |
+
"vision_model.transformer.layers.10.mlp.fc1.bias": "model.safetensors",
|
| 499 |
+
"vision_model.transformer.layers.10.mlp.fc1.weight": "model.safetensors",
|
| 500 |
+
"vision_model.transformer.layers.10.mlp.fc2.bias": "model.safetensors",
|
| 501 |
+
"vision_model.transformer.layers.10.mlp.fc2.weight": "model.safetensors",
|
| 502 |
+
"vision_model.transformer.layers.10.self_attn.out_proj.bias": "model.safetensors",
|
| 503 |
+
"vision_model.transformer.layers.10.self_attn.out_proj.weight": "model.safetensors",
|
| 504 |
+
"vision_model.transformer.layers.10.self_attn.qkv_proj.bias": "model.safetensors",
|
| 505 |
+
"vision_model.transformer.layers.10.self_attn.qkv_proj.weight": "model.safetensors",
|
| 506 |
+
"vision_model.transformer.layers.11.layer_norm1.bias": "model.safetensors",
|
| 507 |
+
"vision_model.transformer.layers.11.layer_norm1.weight": "model.safetensors",
|
| 508 |
+
"vision_model.transformer.layers.11.layer_norm2.bias": "model.safetensors",
|
| 509 |
+
"vision_model.transformer.layers.11.layer_norm2.weight": "model.safetensors",
|
| 510 |
+
"vision_model.transformer.layers.11.mlp.fc1.bias": "model.safetensors",
|
| 511 |
+
"vision_model.transformer.layers.11.mlp.fc1.weight": "model.safetensors",
|
| 512 |
+
"vision_model.transformer.layers.11.mlp.fc2.bias": "model.safetensors",
|
| 513 |
+
"vision_model.transformer.layers.11.mlp.fc2.weight": "model.safetensors",
|
| 514 |
+
"vision_model.transformer.layers.11.self_attn.out_proj.bias": "model.safetensors",
|
| 515 |
+
"vision_model.transformer.layers.11.self_attn.out_proj.weight": "model.safetensors",
|
| 516 |
+
"vision_model.transformer.layers.11.self_attn.qkv_proj.bias": "model.safetensors",
|
| 517 |
+
"vision_model.transformer.layers.11.self_attn.qkv_proj.weight": "model.safetensors",
|
| 518 |
+
"vision_model.transformer.layers.12.layer_norm1.bias": "model.safetensors",
|
| 519 |
+
"vision_model.transformer.layers.12.layer_norm1.weight": "model.safetensors",
|
| 520 |
+
"vision_model.transformer.layers.12.layer_norm2.bias": "model.safetensors",
|
| 521 |
+
"vision_model.transformer.layers.12.layer_norm2.weight": "model.safetensors",
|
| 522 |
+
"vision_model.transformer.layers.12.mlp.fc1.bias": "model.safetensors",
|
| 523 |
+
"vision_model.transformer.layers.12.mlp.fc1.weight": "model.safetensors",
|
| 524 |
+
"vision_model.transformer.layers.12.mlp.fc2.bias": "model.safetensors",
|
| 525 |
+
"vision_model.transformer.layers.12.mlp.fc2.weight": "model.safetensors",
|
| 526 |
+
"vision_model.transformer.layers.12.self_attn.out_proj.bias": "model.safetensors",
|
| 527 |
+
"vision_model.transformer.layers.12.self_attn.out_proj.weight": "model.safetensors",
|
| 528 |
+
"vision_model.transformer.layers.12.self_attn.qkv_proj.bias": "model.safetensors",
|
| 529 |
+
"vision_model.transformer.layers.12.self_attn.qkv_proj.weight": "model.safetensors",
|
| 530 |
+
"vision_model.transformer.layers.13.layer_norm1.bias": "model.safetensors",
|
| 531 |
+
"vision_model.transformer.layers.13.layer_norm1.weight": "model.safetensors",
|
| 532 |
+
"vision_model.transformer.layers.13.layer_norm2.bias": "model.safetensors",
|
| 533 |
+
"vision_model.transformer.layers.13.layer_norm2.weight": "model.safetensors",
|
| 534 |
+
"vision_model.transformer.layers.13.mlp.fc1.bias": "model.safetensors",
|
| 535 |
+
"vision_model.transformer.layers.13.mlp.fc1.weight": "model.safetensors",
|
| 536 |
+
"vision_model.transformer.layers.13.mlp.fc2.bias": "model.safetensors",
|
| 537 |
+
"vision_model.transformer.layers.13.mlp.fc2.weight": "model.safetensors",
|
| 538 |
+
"vision_model.transformer.layers.13.self_attn.out_proj.bias": "model.safetensors",
|
| 539 |
+
"vision_model.transformer.layers.13.self_attn.out_proj.weight": "model.safetensors",
|
| 540 |
+
"vision_model.transformer.layers.13.self_attn.qkv_proj.bias": "model.safetensors",
|
| 541 |
+
"vision_model.transformer.layers.13.self_attn.qkv_proj.weight": "model.safetensors",
|
| 542 |
+
"vision_model.transformer.layers.14.layer_norm1.bias": "model.safetensors",
|
| 543 |
+
"vision_model.transformer.layers.14.layer_norm1.weight": "model.safetensors",
|
| 544 |
+
"vision_model.transformer.layers.14.layer_norm2.bias": "model.safetensors",
|
| 545 |
+
"vision_model.transformer.layers.14.layer_norm2.weight": "model.safetensors",
|
| 546 |
+
"vision_model.transformer.layers.14.mlp.fc1.bias": "model.safetensors",
|
| 547 |
+
"vision_model.transformer.layers.14.mlp.fc1.weight": "model.safetensors",
|
| 548 |
+
"vision_model.transformer.layers.14.mlp.fc2.bias": "model.safetensors",
|
| 549 |
+
"vision_model.transformer.layers.14.mlp.fc2.weight": "model.safetensors",
|
| 550 |
+
"vision_model.transformer.layers.14.self_attn.out_proj.bias": "model.safetensors",
|
| 551 |
+
"vision_model.transformer.layers.14.self_attn.out_proj.weight": "model.safetensors",
|
| 552 |
+
"vision_model.transformer.layers.14.self_attn.qkv_proj.bias": "model.safetensors",
|
| 553 |
+
"vision_model.transformer.layers.14.self_attn.qkv_proj.weight": "model.safetensors",
|
| 554 |
+
"vision_model.transformer.layers.15.layer_norm1.bias": "model.safetensors",
|
| 555 |
+
"vision_model.transformer.layers.15.layer_norm1.weight": "model.safetensors",
|
| 556 |
+
"vision_model.transformer.layers.15.layer_norm2.bias": "model.safetensors",
|
| 557 |
+
"vision_model.transformer.layers.15.layer_norm2.weight": "model.safetensors",
|
| 558 |
+
"vision_model.transformer.layers.15.mlp.fc1.bias": "model.safetensors",
|
| 559 |
+
"vision_model.transformer.layers.15.mlp.fc1.weight": "model.safetensors",
|
| 560 |
+
"vision_model.transformer.layers.15.mlp.fc2.bias": "model.safetensors",
|
| 561 |
+
"vision_model.transformer.layers.15.mlp.fc2.weight": "model.safetensors",
|
| 562 |
+
"vision_model.transformer.layers.15.self_attn.out_proj.bias": "model.safetensors",
|
| 563 |
+
"vision_model.transformer.layers.15.self_attn.out_proj.weight": "model.safetensors",
|
| 564 |
+
"vision_model.transformer.layers.15.self_attn.qkv_proj.bias": "model.safetensors",
|
| 565 |
+
"vision_model.transformer.layers.15.self_attn.qkv_proj.weight": "model.safetensors",
|
| 566 |
+
"vision_model.transformer.layers.16.layer_norm1.bias": "model.safetensors",
|
| 567 |
+
"vision_model.transformer.layers.16.layer_norm1.weight": "model.safetensors",
|
| 568 |
+
"vision_model.transformer.layers.16.layer_norm2.bias": "model.safetensors",
|
| 569 |
+
"vision_model.transformer.layers.16.layer_norm2.weight": "model.safetensors",
|
| 570 |
+
"vision_model.transformer.layers.16.mlp.fc1.bias": "model.safetensors",
|
| 571 |
+
"vision_model.transformer.layers.16.mlp.fc1.weight": "model.safetensors",
|
| 572 |
+
"vision_model.transformer.layers.16.mlp.fc2.bias": "model.safetensors",
|
| 573 |
+
"vision_model.transformer.layers.16.mlp.fc2.weight": "model.safetensors",
|
| 574 |
+
"vision_model.transformer.layers.16.self_attn.out_proj.bias": "model.safetensors",
|
| 575 |
+
"vision_model.transformer.layers.16.self_attn.out_proj.weight": "model.safetensors",
|
| 576 |
+
"vision_model.transformer.layers.16.self_attn.qkv_proj.bias": "model.safetensors",
|
| 577 |
+
"vision_model.transformer.layers.16.self_attn.qkv_proj.weight": "model.safetensors",
|
| 578 |
+
"vision_model.transformer.layers.17.layer_norm1.bias": "model.safetensors",
|
| 579 |
+
"vision_model.transformer.layers.17.layer_norm1.weight": "model.safetensors",
|
| 580 |
+
"vision_model.transformer.layers.17.layer_norm2.bias": "model.safetensors",
|
| 581 |
+
"vision_model.transformer.layers.17.layer_norm2.weight": "model.safetensors",
|
| 582 |
+
"vision_model.transformer.layers.17.mlp.fc1.bias": "model.safetensors",
|
| 583 |
+
"vision_model.transformer.layers.17.mlp.fc1.weight": "model.safetensors",
|
| 584 |
+
"vision_model.transformer.layers.17.mlp.fc2.bias": "model.safetensors",
|
| 585 |
+
"vision_model.transformer.layers.17.mlp.fc2.weight": "model.safetensors",
|
| 586 |
+
"vision_model.transformer.layers.17.self_attn.out_proj.bias": "model.safetensors",
|
| 587 |
+
"vision_model.transformer.layers.17.self_attn.out_proj.weight": "model.safetensors",
|
| 588 |
+
"vision_model.transformer.layers.17.self_attn.qkv_proj.bias": "model.safetensors",
|
| 589 |
+
"vision_model.transformer.layers.17.self_attn.qkv_proj.weight": "model.safetensors",
|
| 590 |
+
"vision_model.transformer.layers.18.layer_norm1.bias": "model.safetensors",
|
| 591 |
+
"vision_model.transformer.layers.18.layer_norm1.weight": "model.safetensors",
|
| 592 |
+
"vision_model.transformer.layers.18.layer_norm2.bias": "model.safetensors",
|
| 593 |
+
"vision_model.transformer.layers.18.layer_norm2.weight": "model.safetensors",
|
| 594 |
+
"vision_model.transformer.layers.18.mlp.fc1.bias": "model.safetensors",
|
| 595 |
+
"vision_model.transformer.layers.18.mlp.fc1.weight": "model.safetensors",
|
| 596 |
+
"vision_model.transformer.layers.18.mlp.fc2.bias": "model.safetensors",
|
| 597 |
+
"vision_model.transformer.layers.18.mlp.fc2.weight": "model.safetensors",
|
| 598 |
+
"vision_model.transformer.layers.18.self_attn.out_proj.bias": "model.safetensors",
|
| 599 |
+
"vision_model.transformer.layers.18.self_attn.out_proj.weight": "model.safetensors",
|
| 600 |
+
"vision_model.transformer.layers.18.self_attn.qkv_proj.bias": "model.safetensors",
|
| 601 |
+
"vision_model.transformer.layers.18.self_attn.qkv_proj.weight": "model.safetensors",
|
| 602 |
+
"vision_model.transformer.layers.19.layer_norm1.bias": "model.safetensors",
|
| 603 |
+
"vision_model.transformer.layers.19.layer_norm1.weight": "model.safetensors",
|
| 604 |
+
"vision_model.transformer.layers.19.layer_norm2.bias": "model.safetensors",
|
| 605 |
+
"vision_model.transformer.layers.19.layer_norm2.weight": "model.safetensors",
|
| 606 |
+
"vision_model.transformer.layers.19.mlp.fc1.bias": "model.safetensors",
|
| 607 |
+
"vision_model.transformer.layers.19.mlp.fc1.weight": "model.safetensors",
|
| 608 |
+
"vision_model.transformer.layers.19.mlp.fc2.bias": "model.safetensors",
|
| 609 |
+
"vision_model.transformer.layers.19.mlp.fc2.weight": "model.safetensors",
|
| 610 |
+
"vision_model.transformer.layers.19.self_attn.out_proj.bias": "model.safetensors",
|
| 611 |
+
"vision_model.transformer.layers.19.self_attn.out_proj.weight": "model.safetensors",
|
| 612 |
+
"vision_model.transformer.layers.19.self_attn.qkv_proj.bias": "model.safetensors",
|
| 613 |
+
"vision_model.transformer.layers.19.self_attn.qkv_proj.weight": "model.safetensors",
|
| 614 |
+
"vision_model.transformer.layers.2.layer_norm1.bias": "model.safetensors",
|
| 615 |
+
"vision_model.transformer.layers.2.layer_norm1.weight": "model.safetensors",
|
| 616 |
+
"vision_model.transformer.layers.2.layer_norm2.bias": "model.safetensors",
|
| 617 |
+
"vision_model.transformer.layers.2.layer_norm2.weight": "model.safetensors",
|
| 618 |
+
"vision_model.transformer.layers.2.mlp.fc1.bias": "model.safetensors",
|
| 619 |
+
"vision_model.transformer.layers.2.mlp.fc1.weight": "model.safetensors",
|
| 620 |
+
"vision_model.transformer.layers.2.mlp.fc2.bias": "model.safetensors",
|
| 621 |
+
"vision_model.transformer.layers.2.mlp.fc2.weight": "model.safetensors",
|
| 622 |
+
"vision_model.transformer.layers.2.self_attn.out_proj.bias": "model.safetensors",
|
| 623 |
+
"vision_model.transformer.layers.2.self_attn.out_proj.weight": "model.safetensors",
|
| 624 |
+
"vision_model.transformer.layers.2.self_attn.qkv_proj.bias": "model.safetensors",
|
| 625 |
+
"vision_model.transformer.layers.2.self_attn.qkv_proj.weight": "model.safetensors",
|
| 626 |
+
"vision_model.transformer.layers.20.layer_norm1.bias": "model.safetensors",
|
| 627 |
+
"vision_model.transformer.layers.20.layer_norm1.weight": "model.safetensors",
|
| 628 |
+
"vision_model.transformer.layers.20.layer_norm2.bias": "model.safetensors",
|
| 629 |
+
"vision_model.transformer.layers.20.layer_norm2.weight": "model.safetensors",
|
| 630 |
+
"vision_model.transformer.layers.20.mlp.fc1.bias": "model.safetensors",
|
| 631 |
+
"vision_model.transformer.layers.20.mlp.fc1.weight": "model.safetensors",
|
| 632 |
+
"vision_model.transformer.layers.20.mlp.fc2.bias": "model.safetensors",
|
| 633 |
+
"vision_model.transformer.layers.20.mlp.fc2.weight": "model.safetensors",
|
| 634 |
+
"vision_model.transformer.layers.20.self_attn.out_proj.bias": "model.safetensors",
|
| 635 |
+
"vision_model.transformer.layers.20.self_attn.out_proj.weight": "model.safetensors",
|
| 636 |
+
"vision_model.transformer.layers.20.self_attn.qkv_proj.bias": "model.safetensors",
|
| 637 |
+
"vision_model.transformer.layers.20.self_attn.qkv_proj.weight": "model.safetensors",
|
| 638 |
+
"vision_model.transformer.layers.21.layer_norm1.bias": "model.safetensors",
|
| 639 |
+
"vision_model.transformer.layers.21.layer_norm1.weight": "model.safetensors",
|
| 640 |
+
"vision_model.transformer.layers.21.layer_norm2.bias": "model.safetensors",
|
| 641 |
+
"vision_model.transformer.layers.21.layer_norm2.weight": "model.safetensors",
|
| 642 |
+
"vision_model.transformer.layers.21.mlp.fc1.bias": "model.safetensors",
|
| 643 |
+
"vision_model.transformer.layers.21.mlp.fc1.weight": "model.safetensors",
|
| 644 |
+
"vision_model.transformer.layers.21.mlp.fc2.bias": "model.safetensors",
|
| 645 |
+
"vision_model.transformer.layers.21.mlp.fc2.weight": "model.safetensors",
|
| 646 |
+
"vision_model.transformer.layers.21.self_attn.out_proj.bias": "model.safetensors",
|
| 647 |
+
"vision_model.transformer.layers.21.self_attn.out_proj.weight": "model.safetensors",
|
| 648 |
+
"vision_model.transformer.layers.21.self_attn.qkv_proj.bias": "model.safetensors",
|
| 649 |
+
"vision_model.transformer.layers.21.self_attn.qkv_proj.weight": "model.safetensors",
|
| 650 |
+
"vision_model.transformer.layers.22.layer_norm1.bias": "model.safetensors",
|
| 651 |
+
"vision_model.transformer.layers.22.layer_norm1.weight": "model.safetensors",
|
| 652 |
+
"vision_model.transformer.layers.22.layer_norm2.bias": "model.safetensors",
|
| 653 |
+
"vision_model.transformer.layers.22.layer_norm2.weight": "model.safetensors",
|
| 654 |
+
"vision_model.transformer.layers.22.mlp.fc1.bias": "model.safetensors",
|
| 655 |
+
"vision_model.transformer.layers.22.mlp.fc1.weight": "model.safetensors",
|
| 656 |
+
"vision_model.transformer.layers.22.mlp.fc2.bias": "model.safetensors",
|
| 657 |
+
"vision_model.transformer.layers.22.mlp.fc2.weight": "model.safetensors",
|
| 658 |
+
"vision_model.transformer.layers.22.self_attn.out_proj.bias": "model.safetensors",
|
| 659 |
+
"vision_model.transformer.layers.22.self_attn.out_proj.weight": "model.safetensors",
|
| 660 |
+
"vision_model.transformer.layers.22.self_attn.qkv_proj.bias": "model.safetensors",
|
| 661 |
+
"vision_model.transformer.layers.22.self_attn.qkv_proj.weight": "model.safetensors",
|
| 662 |
+
"vision_model.transformer.layers.23.layer_norm1.bias": "model.safetensors",
|
| 663 |
+
"vision_model.transformer.layers.23.layer_norm1.weight": "model.safetensors",
|
| 664 |
+
"vision_model.transformer.layers.23.layer_norm2.bias": "model.safetensors",
|
| 665 |
+
"vision_model.transformer.layers.23.layer_norm2.weight": "model.safetensors",
|
| 666 |
+
"vision_model.transformer.layers.23.mlp.fc1.bias": "model.safetensors",
|
| 667 |
+
"vision_model.transformer.layers.23.mlp.fc1.weight": "model.safetensors",
|
| 668 |
+
"vision_model.transformer.layers.23.mlp.fc2.bias": "model.safetensors",
|
| 669 |
+
"vision_model.transformer.layers.23.mlp.fc2.weight": "model.safetensors",
|
| 670 |
+
"vision_model.transformer.layers.23.self_attn.out_proj.bias": "model.safetensors",
|
| 671 |
+
"vision_model.transformer.layers.23.self_attn.out_proj.weight": "model.safetensors",
|
| 672 |
+
"vision_model.transformer.layers.23.self_attn.qkv_proj.bias": "model.safetensors",
|
| 673 |
+
"vision_model.transformer.layers.23.self_attn.qkv_proj.weight": "model.safetensors",
|
| 674 |
+
"vision_model.transformer.layers.3.layer_norm1.bias": "model.safetensors",
|
| 675 |
+
"vision_model.transformer.layers.3.layer_norm1.weight": "model.safetensors",
|
| 676 |
+
"vision_model.transformer.layers.3.layer_norm2.bias": "model.safetensors",
|
| 677 |
+
"vision_model.transformer.layers.3.layer_norm2.weight": "model.safetensors",
|
| 678 |
+
"vision_model.transformer.layers.3.mlp.fc1.bias": "model.safetensors",
|
| 679 |
+
"vision_model.transformer.layers.3.mlp.fc1.weight": "model.safetensors",
|
| 680 |
+
"vision_model.transformer.layers.3.mlp.fc2.bias": "model.safetensors",
|
| 681 |
+
"vision_model.transformer.layers.3.mlp.fc2.weight": "model.safetensors",
|
| 682 |
+
"vision_model.transformer.layers.3.self_attn.out_proj.bias": "model.safetensors",
|
| 683 |
+
"vision_model.transformer.layers.3.self_attn.out_proj.weight": "model.safetensors",
|
| 684 |
+
"vision_model.transformer.layers.3.self_attn.qkv_proj.bias": "model.safetensors",
|
| 685 |
+
"vision_model.transformer.layers.3.self_attn.qkv_proj.weight": "model.safetensors",
|
| 686 |
+
"vision_model.transformer.layers.4.layer_norm1.bias": "model.safetensors",
|
| 687 |
+
"vision_model.transformer.layers.4.layer_norm1.weight": "model.safetensors",
|
| 688 |
+
"vision_model.transformer.layers.4.layer_norm2.bias": "model.safetensors",
|
| 689 |
+
"vision_model.transformer.layers.4.layer_norm2.weight": "model.safetensors",
|
| 690 |
+
"vision_model.transformer.layers.4.mlp.fc1.bias": "model.safetensors",
|
| 691 |
+
"vision_model.transformer.layers.4.mlp.fc1.weight": "model.safetensors",
|
| 692 |
+
"vision_model.transformer.layers.4.mlp.fc2.bias": "model.safetensors",
|
| 693 |
+
"vision_model.transformer.layers.4.mlp.fc2.weight": "model.safetensors",
|
| 694 |
+
"vision_model.transformer.layers.4.self_attn.out_proj.bias": "model.safetensors",
|
| 695 |
+
"vision_model.transformer.layers.4.self_attn.out_proj.weight": "model.safetensors",
|
| 696 |
+
"vision_model.transformer.layers.4.self_attn.qkv_proj.bias": "model.safetensors",
|
| 697 |
+
"vision_model.transformer.layers.4.self_attn.qkv_proj.weight": "model.safetensors",
|
| 698 |
+
"vision_model.transformer.layers.5.layer_norm1.bias": "model.safetensors",
|
| 699 |
+
"vision_model.transformer.layers.5.layer_norm1.weight": "model.safetensors",
|
| 700 |
+
"vision_model.transformer.layers.5.layer_norm2.bias": "model.safetensors",
|
| 701 |
+
"vision_model.transformer.layers.5.layer_norm2.weight": "model.safetensors",
|
| 702 |
+
"vision_model.transformer.layers.5.mlp.fc1.bias": "model.safetensors",
|
| 703 |
+
"vision_model.transformer.layers.5.mlp.fc1.weight": "model.safetensors",
|
| 704 |
+
"vision_model.transformer.layers.5.mlp.fc2.bias": "model.safetensors",
|
| 705 |
+
"vision_model.transformer.layers.5.mlp.fc2.weight": "model.safetensors",
|
| 706 |
+
"vision_model.transformer.layers.5.self_attn.out_proj.bias": "model.safetensors",
|
| 707 |
+
"vision_model.transformer.layers.5.self_attn.out_proj.weight": "model.safetensors",
|
| 708 |
+
"vision_model.transformer.layers.5.self_attn.qkv_proj.bias": "model.safetensors",
|
| 709 |
+
"vision_model.transformer.layers.5.self_attn.qkv_proj.weight": "model.safetensors",
|
| 710 |
+
"vision_model.transformer.layers.6.layer_norm1.bias": "model.safetensors",
|
| 711 |
+
"vision_model.transformer.layers.6.layer_norm1.weight": "model.safetensors",
|
| 712 |
+
"vision_model.transformer.layers.6.layer_norm2.bias": "model.safetensors",
|
| 713 |
+
"vision_model.transformer.layers.6.layer_norm2.weight": "model.safetensors",
|
| 714 |
+
"vision_model.transformer.layers.6.mlp.fc1.bias": "model.safetensors",
|
| 715 |
+
"vision_model.transformer.layers.6.mlp.fc1.weight": "model.safetensors",
|
| 716 |
+
"vision_model.transformer.layers.6.mlp.fc2.bias": "model.safetensors",
|
| 717 |
+
"vision_model.transformer.layers.6.mlp.fc2.weight": "model.safetensors",
|
| 718 |
+
"vision_model.transformer.layers.6.self_attn.out_proj.bias": "model.safetensors",
|
| 719 |
+
"vision_model.transformer.layers.6.self_attn.out_proj.weight": "model.safetensors",
|
| 720 |
+
"vision_model.transformer.layers.6.self_attn.qkv_proj.bias": "model.safetensors",
|
| 721 |
+
"vision_model.transformer.layers.6.self_attn.qkv_proj.weight": "model.safetensors",
|
| 722 |
+
"vision_model.transformer.layers.7.layer_norm1.bias": "model.safetensors",
|
| 723 |
+
"vision_model.transformer.layers.7.layer_norm1.weight": "model.safetensors",
|
| 724 |
+
"vision_model.transformer.layers.7.layer_norm2.bias": "model.safetensors",
|
| 725 |
+
"vision_model.transformer.layers.7.layer_norm2.weight": "model.safetensors",
|
| 726 |
+
"vision_model.transformer.layers.7.mlp.fc1.bias": "model.safetensors",
|
| 727 |
+
"vision_model.transformer.layers.7.mlp.fc1.weight": "model.safetensors",
|
| 728 |
+
"vision_model.transformer.layers.7.mlp.fc2.bias": "model.safetensors",
|
| 729 |
+
"vision_model.transformer.layers.7.mlp.fc2.weight": "model.safetensors",
|
| 730 |
+
"vision_model.transformer.layers.7.self_attn.out_proj.bias": "model.safetensors",
|
| 731 |
+
"vision_model.transformer.layers.7.self_attn.out_proj.weight": "model.safetensors",
|
| 732 |
+
"vision_model.transformer.layers.7.self_attn.qkv_proj.bias": "model.safetensors",
|
| 733 |
+
"vision_model.transformer.layers.7.self_attn.qkv_proj.weight": "model.safetensors",
|
| 734 |
+
"vision_model.transformer.layers.8.layer_norm1.bias": "model.safetensors",
|
| 735 |
+
"vision_model.transformer.layers.8.layer_norm1.weight": "model.safetensors",
|
| 736 |
+
"vision_model.transformer.layers.8.layer_norm2.bias": "model.safetensors",
|
| 737 |
+
"vision_model.transformer.layers.8.layer_norm2.weight": "model.safetensors",
|
| 738 |
+
"vision_model.transformer.layers.8.mlp.fc1.bias": "model.safetensors",
|
| 739 |
+
"vision_model.transformer.layers.8.mlp.fc1.weight": "model.safetensors",
|
| 740 |
+
"vision_model.transformer.layers.8.mlp.fc2.bias": "model.safetensors",
|
| 741 |
+
"vision_model.transformer.layers.8.mlp.fc2.weight": "model.safetensors",
|
| 742 |
+
"vision_model.transformer.layers.8.self_attn.out_proj.bias": "model.safetensors",
|
| 743 |
+
"vision_model.transformer.layers.8.self_attn.out_proj.weight": "model.safetensors",
|
| 744 |
+
"vision_model.transformer.layers.8.self_attn.qkv_proj.bias": "model.safetensors",
|
| 745 |
+
"vision_model.transformer.layers.8.self_attn.qkv_proj.weight": "model.safetensors",
|
| 746 |
+
"vision_model.transformer.layers.9.layer_norm1.bias": "model.safetensors",
|
| 747 |
+
"vision_model.transformer.layers.9.layer_norm1.weight": "model.safetensors",
|
| 748 |
+
"vision_model.transformer.layers.9.layer_norm2.bias": "model.safetensors",
|
| 749 |
+
"vision_model.transformer.layers.9.layer_norm2.weight": "model.safetensors",
|
| 750 |
+
"vision_model.transformer.layers.9.mlp.fc1.bias": "model.safetensors",
|
| 751 |
+
"vision_model.transformer.layers.9.mlp.fc1.weight": "model.safetensors",
|
| 752 |
+
"vision_model.transformer.layers.9.mlp.fc2.bias": "model.safetensors",
|
| 753 |
+
"vision_model.transformer.layers.9.mlp.fc2.weight": "model.safetensors",
|
| 754 |
+
"vision_model.transformer.layers.9.self_attn.out_proj.bias": "model.safetensors",
|
| 755 |
+
"vision_model.transformer.layers.9.self_attn.out_proj.weight": "model.safetensors",
|
| 756 |
+
"vision_model.transformer.layers.9.self_attn.qkv_proj.bias": "model.safetensors",
|
| 757 |
+
"vision_model.transformer.layers.9.self_attn.qkv_proj.weight": "model.safetensors"
|
| 758 |
+
}
|
| 759 |
+
}
|
precision_map.json
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"vision_model": "bfloat16",
|
| 3 |
+
"projector": "mxfp8",
|
| 4 |
+
"language_model.model.embed_tokens": "mxfp8",
|
| 5 |
+
"language_model.model.norm": "bfloat16",
|
| 6 |
+
"language_model.lm_head": "affine8",
|
| 7 |
+
"language_model.model.layers.*.input_layernorm": "bfloat16",
|
| 8 |
+
"language_model.model.layers.*.post_attention_layernorm": "bfloat16",
|
| 9 |
+
"language_model.model.layers.*.self_attn.q_proj": "mxfp8",
|
| 10 |
+
"language_model.model.layers.*.self_attn.k_proj": "mxfp8",
|
| 11 |
+
"language_model.model.layers.*.self_attn.v_proj": "mxfp8",
|
| 12 |
+
"language_model.model.layers.*.self_attn.o_proj": "mxfp8",
|
| 13 |
+
"language_model.model.layers.*.mlp.gate_proj": "mxfp8",
|
| 14 |
+
"language_model.model.layers.*.mlp.up_proj": "mxfp8",
|
| 15 |
+
"language_model.model.layers.*.mlp.down_proj": "mxfp8",
|
| 16 |
+
"language_model.model.layers.*.mlp.shared_experts.gate_proj": "mxfp8",
|
| 17 |
+
"language_model.model.layers.*.mlp.shared_experts.up_proj": "mxfp8",
|
| 18 |
+
"language_model.model.layers.*.mlp.shared_experts.down_proj": "mxfp8",
|
| 19 |
+
"language_model.model.layers.*.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 20 |
+
"language_model.model.layers.*.mlp.switch_mlp.up_proj": "mxfp8",
|
| 21 |
+
"language_model.model.layers.*.mlp.switch_mlp.down_proj": "mxfp8",
|
| 22 |
+
"language_model.model.layers.*.mlp.gate": "bfloat16",
|
| 23 |
+
"sam_model": "bfloat16",
|
| 24 |
+
"_generated_from": {
|
| 25 |
+
"source_model": "baidu/Unlimited-OCR",
|
| 26 |
+
"thresholds": {
|
| 27 |
+
"cer_delta": 0.02,
|
| 28 |
+
"digit_cer_delta": 0.02,
|
| 29 |
+
"table_score_degradation": 0.01
|
| 30 |
+
},
|
| 31 |
+
"decisions": {
|
| 32 |
+
"vision_encoder": {
|
| 33 |
+
"precision": "bfloat16",
|
| 34 |
+
"reasons": [
|
| 35 |
+
"cer_delta=2.423686",
|
| 36 |
+
"digit_cer_delta=1.358023",
|
| 37 |
+
"table_score_degradation=1.000000",
|
| 38 |
+
"protected OCR-critical group"
|
| 39 |
+
]
|
| 40 |
+
},
|
| 41 |
+
"vision_projector": {
|
| 42 |
+
"precision": "mxfp8",
|
| 43 |
+
"reasons": [
|
| 44 |
+
"all measured quality deltas were non-degrading"
|
| 45 |
+
]
|
| 46 |
+
},
|
| 47 |
+
"token_embeddings": {
|
| 48 |
+
"precision": "mxfp8",
|
| 49 |
+
"reasons": [
|
| 50 |
+
"all measured quality deltas were non-degrading"
|
| 51 |
+
]
|
| 52 |
+
},
|
| 53 |
+
"attention_q_proj": {
|
| 54 |
+
"precision": "base-map",
|
| 55 |
+
"reasons": [
|
| 56 |
+
"within thresholds"
|
| 57 |
+
]
|
| 58 |
+
},
|
| 59 |
+
"attention_k_proj": {
|
| 60 |
+
"precision": "base-map",
|
| 61 |
+
"reasons": [
|
| 62 |
+
"within thresholds"
|
| 63 |
+
]
|
| 64 |
+
},
|
| 65 |
+
"attention_v_proj": {
|
| 66 |
+
"precision": "base-map",
|
| 67 |
+
"reasons": [
|
| 68 |
+
"within thresholds"
|
| 69 |
+
]
|
| 70 |
+
},
|
| 71 |
+
"attention_o_proj": {
|
| 72 |
+
"precision": "base-map",
|
| 73 |
+
"reasons": [
|
| 74 |
+
"within thresholds"
|
| 75 |
+
]
|
| 76 |
+
},
|
| 77 |
+
"dense_mlp": {
|
| 78 |
+
"precision": "base-map",
|
| 79 |
+
"reasons": [
|
| 80 |
+
"within thresholds"
|
| 81 |
+
]
|
| 82 |
+
},
|
| 83 |
+
"shared_experts": {
|
| 84 |
+
"precision": "base-map",
|
| 85 |
+
"reasons": [
|
| 86 |
+
"within thresholds"
|
| 87 |
+
]
|
| 88 |
+
},
|
| 89 |
+
"routed_experts": {
|
| 90 |
+
"precision": "base-map",
|
| 91 |
+
"reasons": [
|
| 92 |
+
"within thresholds"
|
| 93 |
+
]
|
| 94 |
+
},
|
| 95 |
+
"lm_head": {
|
| 96 |
+
"precision": "affine8",
|
| 97 |
+
"reasons": [
|
| 98 |
+
"selected by joint quality/throughput calibration: affine8-head"
|
| 99 |
+
]
|
| 100 |
+
}
|
| 101 |
+
},
|
| 102 |
+
"calibration": {
|
| 103 |
+
"selected": {
|
| 104 |
+
"label": "affine8-head",
|
| 105 |
+
"precision": "affine8"
|
| 106 |
+
},
|
| 107 |
+
"selection_policy": "fastest candidate passing existing quality and throughput limits"
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
}
|
preprocessing_config.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"default_mode": "document",
|
| 3 |
+
"modes": {
|
| 4 |
+
"none": {
|
| 5 |
+
"description": "No preprocessing (passthrough)",
|
| 6 |
+
"deskew": false,
|
| 7 |
+
"clahe": false,
|
| 8 |
+
"binarize": false,
|
| 9 |
+
"gamma_correction": false,
|
| 10 |
+
"sharpen": false,
|
| 11 |
+
"grayscale": false,
|
| 12 |
+
"target_size": null
|
| 13 |
+
},
|
| 14 |
+
"auto": {
|
| 15 |
+
"description": "Inspect image and apply needed fixes automatically",
|
| 16 |
+
"deskew": true,
|
| 17 |
+
"clahe": "auto",
|
| 18 |
+
"binarize": false,
|
| 19 |
+
"gamma_correction": "auto",
|
| 20 |
+
"sharpen": "auto",
|
| 21 |
+
"grayscale": false,
|
| 22 |
+
"target_size": 1024
|
| 23 |
+
},
|
| 24 |
+
"document": {
|
| 25 |
+
"description": "Deskew + CLAHE contrast enhancement (default for general documents)",
|
| 26 |
+
"deskew": true,
|
| 27 |
+
"clahe": true,
|
| 28 |
+
"binarize": false,
|
| 29 |
+
"gamma_correction": false,
|
| 30 |
+
"sharpen": false,
|
| 31 |
+
"grayscale": false,
|
| 32 |
+
"target_size": 1024,
|
| 33 |
+
"clahe_clip_limit": 2.0,
|
| 34 |
+
"clahe_grid_size": 8
|
| 35 |
+
},
|
| 36 |
+
"low-contrast": {
|
| 37 |
+
"description": "Aggressive CLAHE + gamma correction for faded scans",
|
| 38 |
+
"deskew": true,
|
| 39 |
+
"clahe": true,
|
| 40 |
+
"binarize": false,
|
| 41 |
+
"gamma_correction": true,
|
| 42 |
+
"sharpen": false,
|
| 43 |
+
"grayscale": false,
|
| 44 |
+
"target_size": 1024,
|
| 45 |
+
"clahe_clip_limit": 4.0,
|
| 46 |
+
"gamma_value": 1.5
|
| 47 |
+
},
|
| 48 |
+
"grayscale": {
|
| 49 |
+
"description": "Convert to grayscale + Otsu binarization",
|
| 50 |
+
"deskew": true,
|
| 51 |
+
"clahe": false,
|
| 52 |
+
"binarize": true,
|
| 53 |
+
"gamma_correction": false,
|
| 54 |
+
"sharpen": false,
|
| 55 |
+
"grayscale": true,
|
| 56 |
+
"target_size": 1024
|
| 57 |
+
},
|
| 58 |
+
"high-resolution": {
|
| 59 |
+
"description": "Resize longest side to 1024 with Lanczos + sharpen",
|
| 60 |
+
"deskew": false,
|
| 61 |
+
"clahe": true,
|
| 62 |
+
"binarize": false,
|
| 63 |
+
"gamma_correction": false,
|
| 64 |
+
"sharpen": true,
|
| 65 |
+
"grayscale": false,
|
| 66 |
+
"target_size": 1024
|
| 67 |
+
},
|
| 68 |
+
"table": {
|
| 69 |
+
"description": "Sharpen + edge enhancement for grid lines and table structure",
|
| 70 |
+
"deskew": true,
|
| 71 |
+
"clahe": true,
|
| 72 |
+
"binarize": false,
|
| 73 |
+
"gamma_correction": false,
|
| 74 |
+
"sharpen": true,
|
| 75 |
+
"grayscale": false,
|
| 76 |
+
"target_size": 1024,
|
| 77 |
+
"clahe_clip_limit": 3.0
|
| 78 |
+
}
|
| 79 |
+
},
|
| 80 |
+
"_note": "Preprocessing is always optional. The raw input path remains available for baseline comparison. Applied steps are recorded in output metadata."
|
| 81 |
+
}
|
processor_config.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_special_token": false,
|
| 3 |
+
"candidate_resolutions": [
|
| 4 |
+
[
|
| 5 |
+
1024,
|
| 6 |
+
1024
|
| 7 |
+
]
|
| 8 |
+
],
|
| 9 |
+
"downsample_ratio": 4,
|
| 10 |
+
"ignore_id": -100,
|
| 11 |
+
"image_mean": [
|
| 12 |
+
0.5,
|
| 13 |
+
0.5,
|
| 14 |
+
0.5
|
| 15 |
+
],
|
| 16 |
+
"image_std": [
|
| 17 |
+
0.5,
|
| 18 |
+
0.5,
|
| 19 |
+
0.5
|
| 20 |
+
],
|
| 21 |
+
"image_token": "<image>",
|
| 22 |
+
"mask_prompt": false,
|
| 23 |
+
"normalize": true,
|
| 24 |
+
"pad_token": "<|▁pad▁|>",
|
| 25 |
+
"patch_size": 16,
|
| 26 |
+
"processor_class": "UnlimitedOCRHFProcessor",
|
| 27 |
+
"sft_format": "unlimitedocr"
|
| 28 |
+
}
|
pyproject.toml
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["hatchling"]
|
| 3 |
+
build-backend = "hatchling.build"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "unlimited-ocr"
|
| 7 |
+
version = "0.2.0"
|
| 8 |
+
description = "Improved MLX inference pipeline for Unlimited-OCR — one-shot long-horizon document parsing on Apple Silicon"
|
| 9 |
+
readme = "README.md"
|
| 10 |
+
license = "MIT"
|
| 11 |
+
requires-python = ">=3.10"
|
| 12 |
+
authors = [
|
| 13 |
+
{ name = "AutomatosX" },
|
| 14 |
+
]
|
| 15 |
+
keywords = ["ocr", "mlx", "apple-silicon", "document-parsing", "vision-language-model"]
|
| 16 |
+
classifiers = [
|
| 17 |
+
"Development Status :: 4 - Beta",
|
| 18 |
+
"Intended Audience :: Developers",
|
| 19 |
+
"License :: OSI Approved :: MIT License",
|
| 20 |
+
"Programming Language :: Python :: 3.10",
|
| 21 |
+
"Programming Language :: Python :: 3.11",
|
| 22 |
+
"Programming Language :: Python :: 3.12",
|
| 23 |
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
| 24 |
+
]
|
| 25 |
+
dependencies = [
|
| 26 |
+
# Unlimited-OCR support first shipped in mlx-vlm 0.6.4. Older releases
|
| 27 |
+
# cannot load the default model used by this package.
|
| 28 |
+
"mlx-vlm>=0.6.4",
|
| 29 |
+
"mlx>=0.31.2",
|
| 30 |
+
"Pillow>=10.0",
|
| 31 |
+
"numpy",
|
| 32 |
+
"pymupdf>=1.24",
|
| 33 |
+
# mlx-vlm already requires the non-headless OpenCV distribution. Requiring
|
| 34 |
+
# the headless wheel as well installs two distributions that own `cv2`.
|
| 35 |
+
"opencv-python>=4.12",
|
| 36 |
+
"click>=8.0",
|
| 37 |
+
"rich>=13.0",
|
| 38 |
+
"huggingface-hub>=0.20",
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
[project.optional-dependencies]
|
| 42 |
+
server = ["fastapi>=0.100", "uvicorn[standard]>=0.20", "python-multipart>=0.0.5"]
|
| 43 |
+
dev = ["pytest>=7.0", "pytest-asyncio>=0.21", "httpx>=0.24"]
|
| 44 |
+
|
| 45 |
+
[project.scripts]
|
| 46 |
+
unlimited-ocr = "unlimited_ocr.cli:main"
|
| 47 |
+
|
| 48 |
+
[project.urls]
|
| 49 |
+
Homepage = "https://huggingface.co/AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8"
|
| 50 |
+
Repository = "https://huggingface.co/AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8"
|
| 51 |
+
|
| 52 |
+
[tool.hatch.build.targets.wheel]
|
| 53 |
+
packages = ["src/unlimited_ocr"]
|
quantization/calibrate_precision.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Select a mixed-precision override from measured candidate experiments."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import json
|
| 8 |
+
import sys
|
| 9 |
+
from datetime import datetime, timezone
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
| 13 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 14 |
+
|
| 15 |
+
from quantization.release_gate import DEFAULT_THRESHOLDS, load_json_object
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
SUPPORTED_HEAD_PRECISIONS = {"bfloat16", "mxfp8", "affine8"}
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _number(payload: dict, key: str) -> float | None:
|
| 22 |
+
value = payload.get(key)
|
| 23 |
+
return float(value) if isinstance(value, (int, float)) and not isinstance(value, bool) else None
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def select_head_precision(
|
| 27 |
+
bf16_accuracy: dict,
|
| 28 |
+
reference_performance: dict,
|
| 29 |
+
experiments: list[dict],
|
| 30 |
+
thresholds: dict | None = None,
|
| 31 |
+
) -> dict:
|
| 32 |
+
"""Choose the fastest experiment that passes the existing release limits."""
|
| 33 |
+
limits = {**DEFAULT_THRESHOLDS, **(thresholds or {})}
|
| 34 |
+
bf16_cer = _number(bf16_accuracy, "mean_cer")
|
| 35 |
+
bf16_digit = _number(bf16_accuracy, "mean_digit_cer")
|
| 36 |
+
bf16_table = _number(bf16_accuracy, "mean_table_score")
|
| 37 |
+
reference_tps = _number(reference_performance, "mean_tps")
|
| 38 |
+
if None in (bf16_cer, bf16_digit, bf16_table, reference_tps) or reference_tps <= 0:
|
| 39 |
+
raise ValueError("Baseline accuracy and reference throughput must be complete")
|
| 40 |
+
if not experiments:
|
| 41 |
+
raise ValueError("At least one calibration experiment is required")
|
| 42 |
+
|
| 43 |
+
evaluated = []
|
| 44 |
+
for experiment in experiments:
|
| 45 |
+
label = experiment.get("label")
|
| 46 |
+
precision = experiment.get("precision")
|
| 47 |
+
accuracy = experiment.get("accuracy")
|
| 48 |
+
performance = experiment.get("performance")
|
| 49 |
+
if not isinstance(label, str) or not label:
|
| 50 |
+
raise ValueError("Each calibration experiment needs a label")
|
| 51 |
+
if precision not in SUPPORTED_HEAD_PRECISIONS:
|
| 52 |
+
raise ValueError(f"Unsupported head precision for {label}: {precision}")
|
| 53 |
+
if not isinstance(accuracy, dict) or not isinstance(performance, dict):
|
| 54 |
+
raise ValueError(f"Calibration metrics are missing for {label}")
|
| 55 |
+
candidate_cer = _number(accuracy, "mean_cer")
|
| 56 |
+
candidate_digit = _number(accuracy, "mean_digit_cer")
|
| 57 |
+
candidate_table = _number(accuracy, "mean_table_score")
|
| 58 |
+
candidate_tps = _number(performance, "mean_tps")
|
| 59 |
+
metrics_complete = None not in (
|
| 60 |
+
candidate_cer, candidate_digit, candidate_table, candidate_tps
|
| 61 |
+
)
|
| 62 |
+
deltas = {
|
| 63 |
+
"cer_vs_bf16": candidate_cer - bf16_cer if metrics_complete else None,
|
| 64 |
+
"digit_cer_vs_bf16": candidate_digit - bf16_digit if metrics_complete else None,
|
| 65 |
+
"table_degradation_vs_bf16": bf16_table - candidate_table if metrics_complete else None,
|
| 66 |
+
"tps_ratio_vs_reference": candidate_tps / reference_tps if metrics_complete else None,
|
| 67 |
+
}
|
| 68 |
+
checks = {
|
| 69 |
+
"cer": metrics_complete and deltas["cer_vs_bf16"] <= limits["max_cer_delta_vs_bf16"],
|
| 70 |
+
"digit_cer": metrics_complete and deltas["digit_cer_vs_bf16"] <= limits["max_digit_cer_delta_vs_bf16"],
|
| 71 |
+
"table_score": metrics_complete and deltas["table_degradation_vs_bf16"] <= limits["max_table_score_degradation_vs_bf16"],
|
| 72 |
+
"throughput": metrics_complete and deltas["tps_ratio_vs_reference"] >= limits["min_tps_ratio_vs_reference"],
|
| 73 |
+
}
|
| 74 |
+
evaluated.append({
|
| 75 |
+
"label": label,
|
| 76 |
+
"precision": precision,
|
| 77 |
+
"passed": all(checks.values()),
|
| 78 |
+
"checks": checks,
|
| 79 |
+
"metrics": {
|
| 80 |
+
"mean_cer": candidate_cer,
|
| 81 |
+
"mean_digit_cer": candidate_digit,
|
| 82 |
+
"mean_table_score": candidate_table,
|
| 83 |
+
"mean_tps": candidate_tps,
|
| 84 |
+
},
|
| 85 |
+
"deltas": deltas,
|
| 86 |
+
})
|
| 87 |
+
|
| 88 |
+
passing = [experiment for experiment in evaluated if experiment["passed"]]
|
| 89 |
+
if not passing:
|
| 90 |
+
raise RuntimeError("No LM-head calibration experiment passed every release limit")
|
| 91 |
+
selected = max(passing, key=lambda experiment: experiment["metrics"]["mean_tps"])
|
| 92 |
+
return {
|
| 93 |
+
"schema_version": 1,
|
| 94 |
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
| 95 |
+
"target_pattern": "language_model.lm_head",
|
| 96 |
+
"selection_policy": "fastest candidate passing existing quality and throughput limits",
|
| 97 |
+
"thresholds": limits,
|
| 98 |
+
"experiments": evaluated,
|
| 99 |
+
"selected": {
|
| 100 |
+
"label": selected["label"],
|
| 101 |
+
"precision": selected["precision"],
|
| 102 |
+
},
|
| 103 |
+
"precision_overrides": {
|
| 104 |
+
"language_model.lm_head": selected["precision"],
|
| 105 |
+
},
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def main() -> None:
|
| 110 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 111 |
+
parser.add_argument("--bf16-accuracy", required=True, type=Path)
|
| 112 |
+
parser.add_argument("--reference-performance", required=True, type=Path)
|
| 113 |
+
parser.add_argument(
|
| 114 |
+
"--experiment",
|
| 115 |
+
action="append",
|
| 116 |
+
nargs=4,
|
| 117 |
+
metavar=("LABEL", "PRECISION", "ACCURACY_JSON", "PERFORMANCE_JSON"),
|
| 118 |
+
required=True,
|
| 119 |
+
)
|
| 120 |
+
parser.add_argument("--output", required=True, type=Path)
|
| 121 |
+
args = parser.parse_args()
|
| 122 |
+
experiments = [
|
| 123 |
+
{
|
| 124 |
+
"label": label,
|
| 125 |
+
"precision": precision,
|
| 126 |
+
"accuracy": load_json_object(accuracy_path),
|
| 127 |
+
"performance": load_json_object(performance_path),
|
| 128 |
+
}
|
| 129 |
+
for label, precision, accuracy_path, performance_path in args.experiment
|
| 130 |
+
]
|
| 131 |
+
result = select_head_precision(
|
| 132 |
+
load_json_object(args.bf16_accuracy),
|
| 133 |
+
load_json_object(args.reference_performance),
|
| 134 |
+
experiments,
|
| 135 |
+
)
|
| 136 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 137 |
+
args.output.write_text(json.dumps(result, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
| 138 |
+
print(json.dumps(result["selected"], ensure_ascii=False))
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
if __name__ == "__main__":
|
| 142 |
+
main()
|
quantization/layer_sensitivity.py
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Layer sensitivity analysis for OCR-aware mixed-precision quantization.
|
| 2 |
+
|
| 3 |
+
This script quantizes one layer group at a time, runs inference on a small
|
| 4 |
+
evaluation set, and measures the quality delta (CER, digit error, table structure)
|
| 5 |
+
relative to the BF16 baseline.
|
| 6 |
+
|
| 7 |
+
Usage:
|
| 8 |
+
python quantization/layer_sensitivity.py \
|
| 9 |
+
--model-path baidu/Unlimited-OCR \
|
| 10 |
+
--eval-dir ./eval_images/ \
|
| 11 |
+
--output sensitivity_results.json
|
| 12 |
+
|
| 13 |
+
Requires: mlx-vlm, mlx, Pillow, numpy
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import argparse
|
| 19 |
+
from collections import Counter
|
| 20 |
+
import gc
|
| 21 |
+
import json
|
| 22 |
+
import sys
|
| 23 |
+
import time
|
| 24 |
+
from pathlib import Path
|
| 25 |
+
|
| 26 |
+
import numpy as np
|
| 27 |
+
|
| 28 |
+
PROJECT_ROOT = Path(__file__).parent.parent
|
| 29 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 30 |
+
sys.path.insert(0, str(PROJECT_ROOT / "src"))
|
| 31 |
+
|
| 32 |
+
from benchmarks.evaluate_cer import compute_cer as _compute_cer
|
| 33 |
+
from benchmarks.evaluate_cer import compute_digit_cer as _compute_digit_cer
|
| 34 |
+
from benchmarks.evaluate_tables import evaluate_tables
|
| 35 |
+
from benchmarks.normalize_output import normalize_ocr_output
|
| 36 |
+
from quantization.mixed_precision_convert import matches_pattern
|
| 37 |
+
from unlimited_ocr.engine import SlidingWindowNoRepeatNGramProcessor
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# Quantizable MLX module groups to test independently. Normalization and the
|
| 41 |
+
# MoE router are raw arrays rather than quantizable MLX layers, so they remain
|
| 42 |
+
# BF16 by construction and are not presented as runnable sensitivity groups.
|
| 43 |
+
LAYER_GROUPS: dict[str, tuple[str, ...]] = {
|
| 44 |
+
"vision_encoder": ("vision_model", "sam_model"),
|
| 45 |
+
"vision_projector": ("projector",),
|
| 46 |
+
"token_embeddings": ("language_model.model.embed_tokens",),
|
| 47 |
+
"attention_q_proj": ("language_model.model.layers.*.self_attn.q_proj",),
|
| 48 |
+
"attention_k_proj": ("language_model.model.layers.*.self_attn.k_proj",),
|
| 49 |
+
"attention_v_proj": ("language_model.model.layers.*.self_attn.v_proj",),
|
| 50 |
+
"attention_o_proj": ("language_model.model.layers.*.self_attn.o_proj",),
|
| 51 |
+
"dense_mlp": (
|
| 52 |
+
"language_model.model.layers.*.mlp.gate_proj",
|
| 53 |
+
"language_model.model.layers.*.mlp.up_proj",
|
| 54 |
+
"language_model.model.layers.*.mlp.down_proj",
|
| 55 |
+
),
|
| 56 |
+
"shared_experts": ("language_model.model.layers.*.mlp.shared_experts",),
|
| 57 |
+
"routed_experts": ("language_model.model.layers.*.mlp.switch_mlp",),
|
| 58 |
+
"lm_head": ("language_model.lm_head",),
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".tiff", ".tif", ".webp", ".bmp"}
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def collect_evaluation_files(
|
| 65 |
+
eval_dir: Path,
|
| 66 |
+
prompt: str = "<image>document parsing.",
|
| 67 |
+
max_tokens: int = 4096,
|
| 68 |
+
) -> list[tuple[Path, Path]]:
|
| 69 |
+
"""Validate an evaluation dataset and return complete image/text pairs."""
|
| 70 |
+
images_dir = eval_dir / "images"
|
| 71 |
+
gt_dir = eval_dir / "ground_truth"
|
| 72 |
+
if not images_dir.is_dir():
|
| 73 |
+
raise FileNotFoundError(f"Images directory not found: {images_dir}")
|
| 74 |
+
if not gt_dir.is_dir():
|
| 75 |
+
raise FileNotFoundError(f"Ground-truth directory not found: {gt_dir}")
|
| 76 |
+
if not isinstance(prompt, str) or prompt.count("<image>") != 1:
|
| 77 |
+
raise ValueError("prompt must contain exactly one literal '<image>' token")
|
| 78 |
+
if not isinstance(max_tokens, int) or isinstance(max_tokens, bool) or max_tokens < 1:
|
| 79 |
+
raise ValueError("max_tokens must be a positive integer")
|
| 80 |
+
|
| 81 |
+
image_files = sorted(
|
| 82 |
+
path for path in images_dir.iterdir()
|
| 83 |
+
if path.is_file() and path.suffix.lower() in IMAGE_EXTENSIONS
|
| 84 |
+
)
|
| 85 |
+
if not image_files:
|
| 86 |
+
raise ValueError(f"No supported images found in {images_dir}")
|
| 87 |
+
|
| 88 |
+
duplicate_stems = sorted(
|
| 89 |
+
stem for stem, count in Counter(path.stem for path in image_files).items()
|
| 90 |
+
if count > 1
|
| 91 |
+
)
|
| 92 |
+
if duplicate_stems:
|
| 93 |
+
raise ValueError(
|
| 94 |
+
"Multiple input images share the same stem: "
|
| 95 |
+
+ ", ".join(duplicate_stems)
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
missing = [
|
| 99 |
+
path.name for path in image_files
|
| 100 |
+
if not (gt_dir / f"{path.stem}.txt").is_file()
|
| 101 |
+
]
|
| 102 |
+
if missing:
|
| 103 |
+
raise FileNotFoundError(
|
| 104 |
+
"Missing ground-truth text for: " + ", ".join(missing)
|
| 105 |
+
)
|
| 106 |
+
return [(path, gt_dir / f"{path.stem}.txt") for path in image_files]
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def compute_cer(reference: str, hypothesis: str) -> float:
|
| 110 |
+
"""Compute Character Error Rate with the shared memory-efficient metric."""
|
| 111 |
+
return _compute_cer(reference, hypothesis)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def compute_digit_cer(reference: str, hypothesis: str) -> float:
|
| 115 |
+
"""Compute CER only on digit characters."""
|
| 116 |
+
return _compute_digit_cer(reference, hypothesis)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def run_inference(model, processor, image_path: str, prompt: str, max_tokens: int = 4096) -> str:
|
| 120 |
+
"""Run a single inference and return the text output."""
|
| 121 |
+
from mlx_vlm import generate
|
| 122 |
+
|
| 123 |
+
response = generate(
|
| 124 |
+
model, processor,
|
| 125 |
+
prompt=prompt,
|
| 126 |
+
image=[image_path],
|
| 127 |
+
max_tokens=max_tokens,
|
| 128 |
+
temperature=0.0,
|
| 129 |
+
logits_processors=[SlidingWindowNoRepeatNGramProcessor(35, 128)],
|
| 130 |
+
verbose=False,
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
if isinstance(response, str):
|
| 134 |
+
return response
|
| 135 |
+
elif hasattr(response, "text"):
|
| 136 |
+
return response.text
|
| 137 |
+
return str(response)
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def evaluate_model(
|
| 141 |
+
model,
|
| 142 |
+
processor,
|
| 143 |
+
eval_dir: Path,
|
| 144 |
+
prompt: str = "<image>document parsing.",
|
| 145 |
+
max_tokens: int = 4096,
|
| 146 |
+
) -> dict:
|
| 147 |
+
"""Evaluate model on a directory of images with ground truth.
|
| 148 |
+
|
| 149 |
+
Expects eval_dir to contain:
|
| 150 |
+
- images/ subdirectory with input images
|
| 151 |
+
- ground_truth/ subdirectory with .txt files (same stem as image)
|
| 152 |
+
"""
|
| 153 |
+
evaluation_files = collect_evaluation_files(eval_dir, prompt, max_tokens)
|
| 154 |
+
|
| 155 |
+
cer_scores = []
|
| 156 |
+
digit_cer_scores = []
|
| 157 |
+
table_scores = []
|
| 158 |
+
total_time = 0.0
|
| 159 |
+
|
| 160 |
+
for img_file, gt_file in evaluation_files:
|
| 161 |
+
reference = normalize_ocr_output(gt_file.read_text(encoding="utf-8"))
|
| 162 |
+
|
| 163 |
+
start = time.perf_counter()
|
| 164 |
+
hypothesis = normalize_ocr_output(run_inference(
|
| 165 |
+
model,
|
| 166 |
+
processor,
|
| 167 |
+
str(img_file),
|
| 168 |
+
prompt,
|
| 169 |
+
max_tokens=max_tokens,
|
| 170 |
+
))
|
| 171 |
+
elapsed = time.perf_counter() - start
|
| 172 |
+
|
| 173 |
+
cer = compute_cer(reference, hypothesis)
|
| 174 |
+
digit_cer = compute_digit_cer(reference, hypothesis)
|
| 175 |
+
|
| 176 |
+
cer_scores.append(cer)
|
| 177 |
+
if any(char.isascii() and char.isdigit() for char in reference):
|
| 178 |
+
digit_cer_scores.append(digit_cer)
|
| 179 |
+
table_result = evaluate_tables(reference, hypothesis)
|
| 180 |
+
if table_result["num_ref_tables"] > 0:
|
| 181 |
+
table_scores.append(table_result["mean_score"])
|
| 182 |
+
total_time += elapsed
|
| 183 |
+
|
| 184 |
+
return {
|
| 185 |
+
"num_samples": len(cer_scores),
|
| 186 |
+
"num_digit_samples": len(digit_cer_scores),
|
| 187 |
+
"num_table_samples": len(table_scores),
|
| 188 |
+
"mean_cer": float(np.mean(cer_scores)) if cer_scores else 0.0,
|
| 189 |
+
"mean_digit_cer": float(np.mean(digit_cer_scores)) if digit_cer_scores else None,
|
| 190 |
+
"mean_table_score": float(np.mean(table_scores)) if table_scores else None,
|
| 191 |
+
"total_inference_time": total_time,
|
| 192 |
+
"avg_time_per_image": total_time / max(len(cer_scores), 1),
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def quantize_layer_group(model, config: dict, group_name: str) -> list[str]:
|
| 197 |
+
"""Quantize one configured MLX module group to MXFP8 in place.
|
| 198 |
+
|
| 199 |
+
Returns the concrete module paths that were converted.
|
| 200 |
+
"""
|
| 201 |
+
if group_name not in LAYER_GROUPS:
|
| 202 |
+
available = ", ".join(LAYER_GROUPS)
|
| 203 |
+
raise ValueError(f"Unknown layer group '{group_name}'. Available: {available}")
|
| 204 |
+
|
| 205 |
+
from mlx_vlm.quant_utils import quantize_model
|
| 206 |
+
|
| 207 |
+
patterns = LAYER_GROUPS[group_name]
|
| 208 |
+
matched: list[str] = []
|
| 209 |
+
|
| 210 |
+
def predicate(path, module):
|
| 211 |
+
if any(matches_pattern(path, pattern) for pattern in patterns):
|
| 212 |
+
matched.append(path)
|
| 213 |
+
return {"group_size": 32, "bits": 8, "mode": "mxfp8"}
|
| 214 |
+
return False
|
| 215 |
+
|
| 216 |
+
quantize_model(
|
| 217 |
+
model,
|
| 218 |
+
config,
|
| 219 |
+
group_size=32,
|
| 220 |
+
bits=8,
|
| 221 |
+
mode="mxfp8",
|
| 222 |
+
quant_predicate=predicate,
|
| 223 |
+
)
|
| 224 |
+
if not matched:
|
| 225 |
+
raise RuntimeError(
|
| 226 |
+
f"Layer group '{group_name}' matched no quantizable MLX modules"
|
| 227 |
+
)
|
| 228 |
+
return sorted(set(matched))
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def main():
|
| 232 |
+
parser = argparse.ArgumentParser(
|
| 233 |
+
description="Layer sensitivity analysis for OCR-aware quantization"
|
| 234 |
+
)
|
| 235 |
+
parser.add_argument("--model-path", required=True,
|
| 236 |
+
help="Path to BF16 model (baidu/Unlimited-OCR or local)")
|
| 237 |
+
parser.add_argument(
|
| 238 |
+
"--source-id",
|
| 239 |
+
default=None,
|
| 240 |
+
help="Public source identifier recorded in results (defaults to repo ID or local name)",
|
| 241 |
+
)
|
| 242 |
+
parser.add_argument("--eval-dir", required=True, type=Path,
|
| 243 |
+
help="Evaluation directory with images/ and ground_truth/")
|
| 244 |
+
parser.add_argument("--output", type=Path, default=Path("sensitivity_results.json"),
|
| 245 |
+
help="Output JSON file for results")
|
| 246 |
+
parser.add_argument("--prompt", default="<image>document parsing.",
|
| 247 |
+
help="OCR prompt to use")
|
| 248 |
+
parser.add_argument("--max-tokens", type=int, default=4096)
|
| 249 |
+
parser.add_argument(
|
| 250 |
+
"--groups",
|
| 251 |
+
nargs="+",
|
| 252 |
+
choices=list(LAYER_GROUPS),
|
| 253 |
+
default=list(LAYER_GROUPS),
|
| 254 |
+
help="Layer groups to test (default: all)",
|
| 255 |
+
)
|
| 256 |
+
args = parser.parse_args()
|
| 257 |
+
if args.max_tokens < 1:
|
| 258 |
+
parser.error("--max-tokens must be positive")
|
| 259 |
+
|
| 260 |
+
# Fail before importing MLX or allocating the model for dataset mistakes.
|
| 261 |
+
collect_evaluation_files(args.eval_dir, args.prompt, args.max_tokens)
|
| 262 |
+
|
| 263 |
+
print("=" * 60)
|
| 264 |
+
print("Layer Sensitivity Analysis for Unlimited-OCR MLX")
|
| 265 |
+
print("=" * 60)
|
| 266 |
+
|
| 267 |
+
# Step 1: Load BF16 baseline
|
| 268 |
+
print("\n[1/3] Loading BF16 baseline model...")
|
| 269 |
+
from mlx_vlm import load
|
| 270 |
+
from mlx_vlm.utils import load_config
|
| 271 |
+
import mlx.core as mx
|
| 272 |
+
|
| 273 |
+
base_config = load_config(args.model_path)
|
| 274 |
+
if base_config.get("quantization") or base_config.get("quantization_config"):
|
| 275 |
+
raise ValueError(
|
| 276 |
+
"Layer sensitivity requires an unquantized BF16 source model"
|
| 277 |
+
)
|
| 278 |
+
model, processor = load(args.model_path)
|
| 279 |
+
|
| 280 |
+
# Step 2: Evaluate baseline
|
| 281 |
+
print("\n[2/3] Evaluating BF16 baseline...")
|
| 282 |
+
baseline = evaluate_model(
|
| 283 |
+
model,
|
| 284 |
+
processor,
|
| 285 |
+
args.eval_dir,
|
| 286 |
+
args.prompt,
|
| 287 |
+
max_tokens=args.max_tokens,
|
| 288 |
+
)
|
| 289 |
+
print(f" Baseline CER: {baseline['mean_cer']:.4f}")
|
| 290 |
+
digit_label = (
|
| 291 |
+
f"{baseline['mean_digit_cer']:.4f}"
|
| 292 |
+
if baseline["mean_digit_cer"] is not None
|
| 293 |
+
else "N/A"
|
| 294 |
+
)
|
| 295 |
+
print(f" Baseline Digit CER: {digit_label}")
|
| 296 |
+
print(f" Samples: {baseline['num_samples']}")
|
| 297 |
+
model = None
|
| 298 |
+
processor = None
|
| 299 |
+
gc.collect()
|
| 300 |
+
mx.clear_cache()
|
| 301 |
+
|
| 302 |
+
# Step 3: Per-group sensitivity
|
| 303 |
+
print("\n[3/3] Testing per-group quantization sensitivity...")
|
| 304 |
+
results = {
|
| 305 |
+
"baseline": baseline,
|
| 306 |
+
"model_path": args.source_id or (
|
| 307 |
+
Path(args.model_path).name if Path(args.model_path).is_dir() else args.model_path
|
| 308 |
+
),
|
| 309 |
+
"eval_dir": args.eval_dir.name,
|
| 310 |
+
"prompt": args.prompt,
|
| 311 |
+
"max_tokens": args.max_tokens,
|
| 312 |
+
"groups_tested": args.groups,
|
| 313 |
+
"layer_groups": {},
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
for group in args.groups:
|
| 317 |
+
print(f"\n Testing: {group}")
|
| 318 |
+
group_model = None
|
| 319 |
+
group_processor = None
|
| 320 |
+
try:
|
| 321 |
+
group_model, group_processor = load(args.model_path)
|
| 322 |
+
matched_modules = quantize_layer_group(
|
| 323 |
+
group_model,
|
| 324 |
+
dict(base_config),
|
| 325 |
+
group,
|
| 326 |
+
)
|
| 327 |
+
metrics = evaluate_model(
|
| 328 |
+
group_model,
|
| 329 |
+
group_processor,
|
| 330 |
+
args.eval_dir,
|
| 331 |
+
args.prompt,
|
| 332 |
+
max_tokens=args.max_tokens,
|
| 333 |
+
)
|
| 334 |
+
digit_delta = (
|
| 335 |
+
metrics["mean_digit_cer"] - baseline["mean_digit_cer"]
|
| 336 |
+
if metrics["mean_digit_cer"] is not None
|
| 337 |
+
and baseline["mean_digit_cer"] is not None
|
| 338 |
+
else None
|
| 339 |
+
)
|
| 340 |
+
table_delta = (
|
| 341 |
+
baseline["mean_table_score"] - metrics["mean_table_score"]
|
| 342 |
+
if metrics["mean_table_score"] is not None
|
| 343 |
+
and baseline["mean_table_score"] is not None
|
| 344 |
+
else None
|
| 345 |
+
)
|
| 346 |
+
results["layer_groups"][group] = {
|
| 347 |
+
"status": "success",
|
| 348 |
+
"matched_module_count": len(matched_modules),
|
| 349 |
+
"matched_modules": matched_modules,
|
| 350 |
+
"metrics": metrics,
|
| 351 |
+
"cer_delta": metrics["mean_cer"] - baseline["mean_cer"],
|
| 352 |
+
"digit_cer_delta": digit_delta,
|
| 353 |
+
"table_score_degradation": table_delta,
|
| 354 |
+
"sensitivity_rank": None,
|
| 355 |
+
}
|
| 356 |
+
print(
|
| 357 |
+
f" CER={metrics['mean_cer']:.4f} "
|
| 358 |
+
f"(delta={metrics['mean_cer'] - baseline['mean_cer']:+.4f})"
|
| 359 |
+
)
|
| 360 |
+
except Exception as exc:
|
| 361 |
+
results["layer_groups"][group] = {
|
| 362 |
+
"status": "error",
|
| 363 |
+
"error": str(exc),
|
| 364 |
+
"sensitivity_rank": None,
|
| 365 |
+
}
|
| 366 |
+
print(f" ERROR: {exc}")
|
| 367 |
+
finally:
|
| 368 |
+
group_model = None
|
| 369 |
+
group_processor = None
|
| 370 |
+
gc.collect()
|
| 371 |
+
mx.clear_cache()
|
| 372 |
+
|
| 373 |
+
successful_groups = [
|
| 374 |
+
(name, data)
|
| 375 |
+
for name, data in results["layer_groups"].items()
|
| 376 |
+
if data["status"] == "success"
|
| 377 |
+
]
|
| 378 |
+
successful_groups.sort(
|
| 379 |
+
key=lambda item: (
|
| 380 |
+
item[1]["cer_delta"],
|
| 381 |
+
item[1]["digit_cer_delta"]
|
| 382 |
+
if item[1]["digit_cer_delta"] is not None
|
| 383 |
+
else float("-inf"),
|
| 384 |
+
),
|
| 385 |
+
reverse=True,
|
| 386 |
+
)
|
| 387 |
+
for rank, (_, data) in enumerate(successful_groups, start=1):
|
| 388 |
+
data["sensitivity_rank"] = rank
|
| 389 |
+
|
| 390 |
+
# Save results
|
| 391 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 392 |
+
args.output.write_text(
|
| 393 |
+
json.dumps(results, indent=2, ensure_ascii=False),
|
| 394 |
+
encoding="utf-8",
|
| 395 |
+
)
|
| 396 |
+
print(f"\nResults saved to: {args.output}")
|
| 397 |
+
if any(data["status"] == "error" for data in results["layer_groups"].values()):
|
| 398 |
+
raise SystemExit(1)
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
if __name__ == "__main__":
|
| 402 |
+
main()
|
quantization/mixed_precision_convert.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Mixed-precision conversion script for OCR-aware quantization.
|
| 2 |
+
|
| 3 |
+
Applies a precision map to produce enhanced model weights where OCR-sensitive
|
| 4 |
+
layers remain at BF16 while less sensitive layers use MXFP8.
|
| 5 |
+
|
| 6 |
+
Usage:
|
| 7 |
+
python quantization/mixed_precision_convert.py \
|
| 8 |
+
--model-path baidu/Unlimited-OCR \
|
| 9 |
+
--precision-map quantization/precision_map.json \
|
| 10 |
+
--output-dir ./enhanced_model/
|
| 11 |
+
|
| 12 |
+
Requires: mlx, mlx-vlm
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import argparse
|
| 18 |
+
import hashlib
|
| 19 |
+
import json
|
| 20 |
+
import re
|
| 21 |
+
import shutil
|
| 22 |
+
import tempfile
|
| 23 |
+
from pathlib import Path
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
SUPPORTED_PRECISIONS = {"bfloat16", "mxfp8", "affine8"}
|
| 27 |
+
QUANTIZATION_CONFIGS = {
|
| 28 |
+
"mxfp8": {"group_size": 32, "bits": 8, "mode": "mxfp8"},
|
| 29 |
+
"affine8": {"group_size": 32, "bits": 8, "mode": "affine"},
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _json_digest(value: dict) -> str:
|
| 34 |
+
payload = json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
| 35 |
+
return hashlib.sha256(payload).hexdigest()
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def normalize_unlimited_ocr_metadata(model_dir: Path) -> None:
|
| 39 |
+
"""Select mlx-vlm's native backend and validate MXFP8/R-SWA metadata.
|
| 40 |
+
|
| 41 |
+
``mlx-vlm convert`` always emits ``config.json`` (and usually
|
| 42 |
+
``processor_config.json``). Missing configs mean conversion did not finish
|
| 43 |
+
and must not be published.
|
| 44 |
+
"""
|
| 45 |
+
config_path = model_dir / "config.json"
|
| 46 |
+
processor_path = model_dir / "processor_config.json"
|
| 47 |
+
if not config_path.is_file():
|
| 48 |
+
raise ValueError(
|
| 49 |
+
"Converted model is missing config.json — mlx-vlm conversion incomplete"
|
| 50 |
+
)
|
| 51 |
+
try:
|
| 52 |
+
config = json.loads(config_path.read_text(encoding="utf-8"))
|
| 53 |
+
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
| 54 |
+
raise ValueError("Converted model has invalid config.json") from exc
|
| 55 |
+
if not isinstance(config, dict):
|
| 56 |
+
raise ValueError("Converted config.json must contain a JSON object")
|
| 57 |
+
|
| 58 |
+
architectures = config.get("architectures")
|
| 59 |
+
if not isinstance(architectures, list) or "UnlimitedOCRForCausalLM" not in architectures:
|
| 60 |
+
raise ValueError("Converted checkpoint is not UnlimitedOCRForCausalLM")
|
| 61 |
+
quantization = config.get("quantization") or config.get("quantization_config")
|
| 62 |
+
if not isinstance(quantization, dict) or quantization.get("mode") != "mxfp8":
|
| 63 |
+
raise ValueError("Converted checkpoint does not declare MXFP8 quantization")
|
| 64 |
+
text_config = config.get("language_config") or config.get("text_config") or config
|
| 65 |
+
window_size = None
|
| 66 |
+
if isinstance(text_config, dict):
|
| 67 |
+
window_size = text_config.get("sliding_window_size", text_config.get("sliding_window"))
|
| 68 |
+
if window_size is None:
|
| 69 |
+
window_size = config.get("sliding_window_size", config.get("sliding_window"))
|
| 70 |
+
if not isinstance(window_size, int) or isinstance(window_size, bool) or window_size < 1:
|
| 71 |
+
raise ValueError("Converted checkpoint is missing a positive sliding-window size")
|
| 72 |
+
|
| 73 |
+
config["model_type"] = "unlimited-ocr"
|
| 74 |
+
config.pop("auto_map", None)
|
| 75 |
+
# Keep R-SWA fields consistent at the top level and under language_config.
|
| 76 |
+
config["sliding_window"] = int(window_size)
|
| 77 |
+
config["sliding_window_size"] = int(window_size)
|
| 78 |
+
language_config = config.get("language_config")
|
| 79 |
+
if isinstance(language_config, dict):
|
| 80 |
+
language_config.pop("auto_map", None)
|
| 81 |
+
language_config["sliding_window"] = int(window_size)
|
| 82 |
+
language_config["sliding_window_size"] = int(window_size)
|
| 83 |
+
|
| 84 |
+
if processor_path.is_file():
|
| 85 |
+
try:
|
| 86 |
+
processor = json.loads(processor_path.read_text(encoding="utf-8"))
|
| 87 |
+
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
| 88 |
+
raise ValueError("Converted model has invalid processor_config.json") from exc
|
| 89 |
+
if not isinstance(processor, dict):
|
| 90 |
+
raise ValueError("Converted processor_config.json must contain a JSON object")
|
| 91 |
+
else:
|
| 92 |
+
# Some convert paths only emit tokenizer assets; still mark the processor
|
| 93 |
+
# class so mlx-vlm loads the Unlimited-OCR handler.
|
| 94 |
+
processor = {}
|
| 95 |
+
|
| 96 |
+
processor["processor_class"] = "UnlimitedOCRHFProcessor"
|
| 97 |
+
processor["sft_format"] = "unlimitedocr"
|
| 98 |
+
config_path.write_text(
|
| 99 |
+
json.dumps(config, indent=2, ensure_ascii=False) + "\n",
|
| 100 |
+
encoding="utf-8",
|
| 101 |
+
)
|
| 102 |
+
processor_path.write_text(
|
| 103 |
+
json.dumps(processor, indent=2, ensure_ascii=False) + "\n",
|
| 104 |
+
encoding="utf-8",
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def load_precision_map(path: Path) -> dict:
|
| 109 |
+
"""Load the precision map JSON."""
|
| 110 |
+
with open(path, encoding="utf-8") as f:
|
| 111 |
+
precision_map = json.load(f)
|
| 112 |
+
validate_precision_map(precision_map)
|
| 113 |
+
return precision_map
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def validate_precision_map(precision_map: dict) -> None:
|
| 117 |
+
"""Validate precision-map structure and supported precision values."""
|
| 118 |
+
if not isinstance(precision_map, dict):
|
| 119 |
+
raise ValueError("precision map must be a JSON object")
|
| 120 |
+
rules = {
|
| 121 |
+
pattern: precision
|
| 122 |
+
for pattern, precision in precision_map.items()
|
| 123 |
+
if not pattern.startswith("_")
|
| 124 |
+
}
|
| 125 |
+
if not rules:
|
| 126 |
+
raise ValueError("precision map has no module rules")
|
| 127 |
+
invalid = {
|
| 128 |
+
pattern: precision
|
| 129 |
+
for pattern, precision in rules.items()
|
| 130 |
+
if not isinstance(precision, str) or precision not in SUPPORTED_PRECISIONS
|
| 131 |
+
}
|
| 132 |
+
if invalid:
|
| 133 |
+
details = ", ".join(f"{pattern}={precision!r}" for pattern, precision in invalid.items())
|
| 134 |
+
raise ValueError(f"unsupported precision-map values: {details}")
|
| 135 |
+
if "mxfp8" not in rules.values():
|
| 136 |
+
raise ValueError("precision map does not select any modules for MXFP8")
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def matches_pattern(param_name: str, pattern: str) -> bool:
|
| 140 |
+
"""Check if a parameter name matches a precision map pattern.
|
| 141 |
+
|
| 142 |
+
Supports wildcard '*' for layer indices.
|
| 143 |
+
Example: ``model.layers.*.self_attn.q_proj`` matches layer 5's q_proj.
|
| 144 |
+
"""
|
| 145 |
+
regex = re.escape(pattern).replace(r"\*", r"\d+")
|
| 146 |
+
# Precision-map entries name complete module-path segments but parameter
|
| 147 |
+
# names may contain a model prefix and a trailing ``.weight``.
|
| 148 |
+
return bool(re.search(rf"(?:^|\.){regex}(?=\.|$)", param_name))
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def get_precision_for_param(param_name: str, precision_map: dict) -> str:
|
| 152 |
+
"""Determine the target precision for a given parameter name."""
|
| 153 |
+
matching_rules = [
|
| 154 |
+
(index, pattern, precision)
|
| 155 |
+
for index, (pattern, precision) in enumerate(precision_map.items())
|
| 156 |
+
if not pattern.startswith("_") and matches_pattern(param_name, pattern)
|
| 157 |
+
]
|
| 158 |
+
if matching_rules:
|
| 159 |
+
# Exact per-layer sensitivity overrides must beat an earlier wildcard
|
| 160 |
+
# group rule. More literal path segments are more specific; insertion
|
| 161 |
+
# order is only a tie breaker.
|
| 162 |
+
_, _, precision = max(
|
| 163 |
+
matching_rules,
|
| 164 |
+
key=lambda item: (
|
| 165 |
+
len([segment for segment in item[1].split(".") if segment != "*"]),
|
| 166 |
+
len(item[1].split(".")),
|
| 167 |
+
item[0],
|
| 168 |
+
),
|
| 169 |
+
)
|
| 170 |
+
return precision
|
| 171 |
+
# Unlisted modules are preserved. Quantizing by a loose name heuristic can
|
| 172 |
+
# accidentally include the vision tower or projector.
|
| 173 |
+
return "bfloat16"
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def convert_model(
|
| 177 |
+
model_path: str,
|
| 178 |
+
precision_map: dict,
|
| 179 |
+
output_dir: Path,
|
| 180 |
+
verbose: bool = True,
|
| 181 |
+
) -> Path:
|
| 182 |
+
"""Apply mixed-precision quantization according to the precision map.
|
| 183 |
+
|
| 184 |
+
The conversion is staged in a temporary sibling directory and moved into
|
| 185 |
+
place only after quantized weights and all requested rule matches exist.
|
| 186 |
+
"""
|
| 187 |
+
validate_precision_map(precision_map)
|
| 188 |
+
output_dir = Path(output_dir)
|
| 189 |
+
if output_dir.exists():
|
| 190 |
+
raise FileExistsError(
|
| 191 |
+
f"Output directory already exists; choose a new path: {output_dir}"
|
| 192 |
+
)
|
| 193 |
+
output_dir.parent.mkdir(parents=True, exist_ok=True)
|
| 194 |
+
|
| 195 |
+
from mlx_vlm.convert import convert
|
| 196 |
+
|
| 197 |
+
if verbose:
|
| 198 |
+
print(f"Loading and converting model from: {model_path}")
|
| 199 |
+
|
| 200 |
+
quantized_modules: list[str] = []
|
| 201 |
+
quantized_precisions: dict[str, str] = {}
|
| 202 |
+
preserved_modules: list[str] = []
|
| 203 |
+
|
| 204 |
+
def quantization_predicate(path, module):
|
| 205 |
+
precision = get_precision_for_param(path, precision_map)
|
| 206 |
+
if precision in QUANTIZATION_CONFIGS:
|
| 207 |
+
quantized_modules.append(path)
|
| 208 |
+
quantized_precisions[path] = precision
|
| 209 |
+
return dict(QUANTIZATION_CONFIGS[precision])
|
| 210 |
+
preserved_modules.append(path)
|
| 211 |
+
return False
|
| 212 |
+
|
| 213 |
+
if verbose:
|
| 214 |
+
print(f"\nPrecision map summary:")
|
| 215 |
+
for pattern, precision in precision_map.items():
|
| 216 |
+
if not pattern.startswith("_"):
|
| 217 |
+
print(f" {pattern}: {precision}")
|
| 218 |
+
|
| 219 |
+
staging_dir = Path(tempfile.mkdtemp(
|
| 220 |
+
prefix=f".{output_dir.name}_staging_",
|
| 221 |
+
dir=output_dir.parent,
|
| 222 |
+
))
|
| 223 |
+
try:
|
| 224 |
+
convert(
|
| 225 |
+
hf_path=model_path,
|
| 226 |
+
mlx_path=str(staging_dir),
|
| 227 |
+
quantize=True,
|
| 228 |
+
q_group_size=32,
|
| 229 |
+
q_bits=8,
|
| 230 |
+
q_mode="mxfp8",
|
| 231 |
+
quant_predicate=quantization_predicate,
|
| 232 |
+
)
|
| 233 |
+
|
| 234 |
+
if not quantized_modules:
|
| 235 |
+
raise RuntimeError(
|
| 236 |
+
"Precision map matched no quantizable MLX modules; refusing to save a BF16-only model"
|
| 237 |
+
)
|
| 238 |
+
requested_patterns = [
|
| 239 |
+
pattern
|
| 240 |
+
for pattern, precision in precision_map.items()
|
| 241 |
+
if not pattern.startswith("_") and precision in QUANTIZATION_CONFIGS
|
| 242 |
+
]
|
| 243 |
+
unmatched_patterns = [
|
| 244 |
+
pattern
|
| 245 |
+
for pattern in requested_patterns
|
| 246 |
+
if not any(matches_pattern(path, pattern) for path in quantized_modules)
|
| 247 |
+
]
|
| 248 |
+
if unmatched_patterns:
|
| 249 |
+
raise RuntimeError(
|
| 250 |
+
"MXFP8 precision rules matched no modules: " + ", ".join(unmatched_patterns)
|
| 251 |
+
)
|
| 252 |
+
if not list(staging_dir.glob("*.safetensors")):
|
| 253 |
+
raise RuntimeError("mlx-vlm conversion produced no safetensors weights")
|
| 254 |
+
|
| 255 |
+
normalize_unlimited_ocr_metadata(staging_dir)
|
| 256 |
+
|
| 257 |
+
(staging_dir / "precision_map.json").write_text(
|
| 258 |
+
json.dumps(precision_map, indent=2),
|
| 259 |
+
encoding="utf-8",
|
| 260 |
+
)
|
| 261 |
+
(staging_dir / "quantization_summary.json").write_text(
|
| 262 |
+
json.dumps({
|
| 263 |
+
"method": "mxfp8",
|
| 264 |
+
"group_size": 32,
|
| 265 |
+
"bits": 8,
|
| 266 |
+
"source_model": (
|
| 267 |
+
Path(model_path).name if Path(model_path).is_dir() else model_path
|
| 268 |
+
),
|
| 269 |
+
"precision_map_sha256": _json_digest(precision_map),
|
| 270 |
+
"quantized_module_count": len(set(quantized_modules)),
|
| 271 |
+
"quantized_precision_counts": {
|
| 272 |
+
precision: sum(
|
| 273 |
+
selected == precision
|
| 274 |
+
for selected in quantized_precisions.values()
|
| 275 |
+
)
|
| 276 |
+
for precision in sorted(set(quantized_precisions.values()))
|
| 277 |
+
},
|
| 278 |
+
"preserved_quantizable_module_count": len(set(preserved_modules)),
|
| 279 |
+
"quantized_modules": sorted(set(quantized_modules)),
|
| 280 |
+
"quantized_module_precisions": dict(sorted(quantized_precisions.items())),
|
| 281 |
+
}, indent=2),
|
| 282 |
+
encoding="utf-8",
|
| 283 |
+
)
|
| 284 |
+
staging_dir.replace(output_dir)
|
| 285 |
+
finally:
|
| 286 |
+
if staging_dir.exists():
|
| 287 |
+
shutil.rmtree(staging_dir)
|
| 288 |
+
|
| 289 |
+
if verbose:
|
| 290 |
+
print(f"\nQuantized {len(set(quantized_modules))} module(s).")
|
| 291 |
+
print(f"Model saved to: {output_dir}")
|
| 292 |
+
return output_dir
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
def main():
|
| 296 |
+
parser = argparse.ArgumentParser(
|
| 297 |
+
description="Apply OCR-aware mixed-precision quantization"
|
| 298 |
+
)
|
| 299 |
+
parser.add_argument("--model-path", required=True,
|
| 300 |
+
help="Source model (baidu/Unlimited-OCR or local path)")
|
| 301 |
+
parser.add_argument("--precision-map", type=Path,
|
| 302 |
+
default=Path(__file__).parent / "precision_map.json",
|
| 303 |
+
help="Path to precision_map.json")
|
| 304 |
+
parser.add_argument("--output-dir", type=Path, default=Path("./enhanced_model"),
|
| 305 |
+
help="Output directory for enhanced model")
|
| 306 |
+
parser.add_argument(
|
| 307 |
+
"--verbose",
|
| 308 |
+
action=argparse.BooleanOptionalAction,
|
| 309 |
+
default=True,
|
| 310 |
+
help="Show conversion progress (use --no-verbose to suppress)",
|
| 311 |
+
)
|
| 312 |
+
args = parser.parse_args()
|
| 313 |
+
|
| 314 |
+
print("=" * 60)
|
| 315 |
+
print("Unlimited-OCR Mixed-Precision Conversion")
|
| 316 |
+
print("=" * 60)
|
| 317 |
+
|
| 318 |
+
precision_map = load_precision_map(args.precision_map)
|
| 319 |
+
convert_model(args.model_path, precision_map, args.output_dir, args.verbose)
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
if __name__ == "__main__":
|
| 323 |
+
main()
|
quantization/precision_map.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"vision_model": "bfloat16",
|
| 3 |
+
"projector": "bfloat16",
|
| 4 |
+
"language_model.model.embed_tokens": "bfloat16",
|
| 5 |
+
"language_model.model.norm": "bfloat16",
|
| 6 |
+
"language_model.lm_head": "bfloat16",
|
| 7 |
+
"language_model.model.layers.*.input_layernorm": "bfloat16",
|
| 8 |
+
"language_model.model.layers.*.post_attention_layernorm": "bfloat16",
|
| 9 |
+
"language_model.model.layers.*.self_attn.q_proj": "mxfp8",
|
| 10 |
+
"language_model.model.layers.*.self_attn.k_proj": "mxfp8",
|
| 11 |
+
"language_model.model.layers.*.self_attn.v_proj": "mxfp8",
|
| 12 |
+
"language_model.model.layers.*.self_attn.o_proj": "mxfp8",
|
| 13 |
+
"language_model.model.layers.*.mlp.gate_proj": "mxfp8",
|
| 14 |
+
"language_model.model.layers.*.mlp.up_proj": "mxfp8",
|
| 15 |
+
"language_model.model.layers.*.mlp.down_proj": "mxfp8",
|
| 16 |
+
"language_model.model.layers.*.mlp.shared_experts.gate_proj": "mxfp8",
|
| 17 |
+
"language_model.model.layers.*.mlp.shared_experts.up_proj": "mxfp8",
|
| 18 |
+
"language_model.model.layers.*.mlp.shared_experts.down_proj": "mxfp8",
|
| 19 |
+
"language_model.model.layers.*.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 20 |
+
"language_model.model.layers.*.mlp.switch_mlp.up_proj": "mxfp8",
|
| 21 |
+
"language_model.model.layers.*.mlp.switch_mlp.down_proj": "mxfp8",
|
| 22 |
+
"language_model.model.layers.*.mlp.gate": "bfloat16",
|
| 23 |
+
"_comment": "MLX module paths for DeepseekOCR. Refine via layer_sensitivity.py before final release.",
|
| 24 |
+
"_sensitivity_override": {
|
| 25 |
+
"_description": "Layers identified as OCR-sensitive by sensitivity analysis should be promoted to bfloat16. Run layer_sensitivity.py to populate this section.",
|
| 26 |
+
"examples": [
|
| 27 |
+
"language_model.model.layers.11.self_attn.q_proj -> bfloat16 (if digit CER degrades >2%)",
|
| 28 |
+
"language_model.model.layers.1.mlp.switch_mlp -> bfloat16 (if table score degrades >1%)"
|
| 29 |
+
]
|
| 30 |
+
}
|
| 31 |
+
}
|
quantization/release_gate.py
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Create a content-addressed, fail-closed model release manifest."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import hashlib
|
| 8 |
+
import importlib.metadata
|
| 9 |
+
import json
|
| 10 |
+
import platform
|
| 11 |
+
from collections import Counter
|
| 12 |
+
from datetime import datetime, timezone
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
from typing import Any
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".tiff", ".tif", ".webp", ".bmp"}
|
| 18 |
+
HARD_PROTECTED_GROUPS = {"vision_encoder", "lm_head"}
|
| 19 |
+
EVIDENCE_PROTECTED_GROUPS = {"vision_projector", "token_embeddings"}
|
| 20 |
+
DEFAULT_THRESHOLDS = {
|
| 21 |
+
"max_cer_delta_vs_bf16": 0.01,
|
| 22 |
+
"max_cer_delta_vs_reference": 0.005,
|
| 23 |
+
"max_digit_cer_delta_vs_bf16": 0.01,
|
| 24 |
+
"max_table_score_degradation_vs_bf16": 0.01,
|
| 25 |
+
"min_tps_ratio_vs_reference": 0.90,
|
| 26 |
+
"max_weight_size_gb": 4.5,
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def load_json_object(path: str | Path) -> dict:
|
| 31 |
+
"""Load a JSON object and reject malformed release evidence."""
|
| 32 |
+
path = Path(path)
|
| 33 |
+
try:
|
| 34 |
+
value = json.loads(path.read_text(encoding="utf-8"))
|
| 35 |
+
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
| 36 |
+
raise ValueError(f"Invalid JSON file: {path}") from exc
|
| 37 |
+
if not isinstance(value, dict):
|
| 38 |
+
raise ValueError(f"JSON file must contain an object: {path}")
|
| 39 |
+
return value
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def sha256_file(path: str | Path, chunk_size: int = 8 * 1024 * 1024) -> str:
|
| 43 |
+
"""Return the SHA-256 digest of a file without loading it into memory."""
|
| 44 |
+
digest = hashlib.sha256()
|
| 45 |
+
with Path(path).open("rb") as handle:
|
| 46 |
+
while chunk := handle.read(chunk_size):
|
| 47 |
+
digest.update(chunk)
|
| 48 |
+
return digest.hexdigest()
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def model_weight_manifest(model_dir: str | Path) -> dict:
|
| 52 |
+
"""Hash Safetensors files and create a stable aggregate digest."""
|
| 53 |
+
model_dir = Path(model_dir)
|
| 54 |
+
weight_paths = sorted(path for path in model_dir.glob("*.safetensors") if path.is_file())
|
| 55 |
+
if not weight_paths:
|
| 56 |
+
raise FileNotFoundError(f"No Safetensors weights found in: {model_dir}")
|
| 57 |
+
|
| 58 |
+
aggregate = hashlib.sha256()
|
| 59 |
+
files = []
|
| 60 |
+
total_size = 0
|
| 61 |
+
for path in weight_paths:
|
| 62 |
+
size = path.stat().st_size
|
| 63 |
+
digest = sha256_file(path)
|
| 64 |
+
total_size += size
|
| 65 |
+
aggregate.update(path.name.encode("utf-8"))
|
| 66 |
+
aggregate.update(b"\0")
|
| 67 |
+
aggregate.update(str(size).encode("ascii"))
|
| 68 |
+
aggregate.update(b"\0")
|
| 69 |
+
aggregate.update(digest.encode("ascii"))
|
| 70 |
+
aggregate.update(b"\n")
|
| 71 |
+
files.append({"name": path.name, "size": size, "sha256": digest})
|
| 72 |
+
|
| 73 |
+
return {
|
| 74 |
+
"files": files,
|
| 75 |
+
"total_size_bytes": total_size,
|
| 76 |
+
"total_size_gb": total_size / (1024**3),
|
| 77 |
+
"aggregate_sha256": aggregate.hexdigest(),
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def dataset_manifest(eval_dir: str | Path) -> dict:
|
| 82 |
+
"""Validate and content-hash an OCR image/ground-truth dataset."""
|
| 83 |
+
eval_dir = Path(eval_dir)
|
| 84 |
+
images_dir = eval_dir / "images"
|
| 85 |
+
ground_truth_dir = eval_dir / "ground_truth"
|
| 86 |
+
manifest_path = eval_dir / "manifest.json"
|
| 87 |
+
if not images_dir.is_dir() or not ground_truth_dir.is_dir():
|
| 88 |
+
raise FileNotFoundError("Evaluation dataset requires images/ and ground_truth/")
|
| 89 |
+
declared = load_json_object(manifest_path)
|
| 90 |
+
|
| 91 |
+
image_paths = sorted(
|
| 92 |
+
path for path in images_dir.iterdir()
|
| 93 |
+
if path.is_file() and path.suffix.lower() in IMAGE_EXTENSIONS
|
| 94 |
+
)
|
| 95 |
+
if not image_paths:
|
| 96 |
+
raise ValueError(f"Evaluation dataset contains no images: {images_dir}")
|
| 97 |
+
duplicate_stems = sorted(
|
| 98 |
+
stem for stem, count in Counter(path.stem for path in image_paths).items()
|
| 99 |
+
if count > 1
|
| 100 |
+
)
|
| 101 |
+
if duplicate_stems:
|
| 102 |
+
raise ValueError("Duplicate image stems: " + ", ".join(duplicate_stems))
|
| 103 |
+
|
| 104 |
+
declared_samples = declared.get("samples", [])
|
| 105 |
+
if not isinstance(declared_samples, list) or not declared_samples:
|
| 106 |
+
raise ValueError("manifest.json must declare at least one sample")
|
| 107 |
+
samples_by_image = {
|
| 108 |
+
sample.get("image"): sample
|
| 109 |
+
for sample in declared_samples
|
| 110 |
+
if isinstance(sample, dict) and isinstance(sample.get("image"), str)
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
content_digest = hashlib.sha256()
|
| 114 |
+
files = []
|
| 115 |
+
category_counts: Counter[str] = Counter()
|
| 116 |
+
digit_samples = 0
|
| 117 |
+
cjk_samples = 0
|
| 118 |
+
table_samples = 0
|
| 119 |
+
for image_path in image_paths:
|
| 120 |
+
relative_image = image_path.relative_to(eval_dir).as_posix()
|
| 121 |
+
sample = samples_by_image.get(relative_image)
|
| 122 |
+
if sample is None:
|
| 123 |
+
raise ValueError(f"Image is missing from manifest.json: {relative_image}")
|
| 124 |
+
gt_path = ground_truth_dir / f"{image_path.stem}.txt"
|
| 125 |
+
if not gt_path.is_file():
|
| 126 |
+
raise FileNotFoundError(f"Missing ground truth for: {image_path.name}")
|
| 127 |
+
ground_truth = gt_path.read_text(encoding="utf-8")
|
| 128 |
+
if not ground_truth.strip():
|
| 129 |
+
raise ValueError(f"Ground truth is empty: {gt_path}")
|
| 130 |
+
|
| 131 |
+
category_counts[str(sample.get("category", "uncategorized"))] += 1
|
| 132 |
+
digit_samples += int(any(character.isascii() and character.isdigit() for character in ground_truth))
|
| 133 |
+
cjk_samples += int(any(
|
| 134 |
+
"\u3040" <= character <= "\u30ff"
|
| 135 |
+
or "\u3400" <= character <= "\u9fff"
|
| 136 |
+
or "\uac00" <= character <= "\ud7af"
|
| 137 |
+
for character in ground_truth
|
| 138 |
+
))
|
| 139 |
+
table_samples += int("<table" in ground_truth.lower() or "| ---" in ground_truth)
|
| 140 |
+
|
| 141 |
+
for path in (image_path, gt_path):
|
| 142 |
+
relative = path.relative_to(eval_dir).as_posix()
|
| 143 |
+
digest = sha256_file(path)
|
| 144 |
+
size = path.stat().st_size
|
| 145 |
+
content_digest.update(relative.encode("utf-8"))
|
| 146 |
+
content_digest.update(b"\0")
|
| 147 |
+
content_digest.update(digest.encode("ascii"))
|
| 148 |
+
content_digest.update(b"\n")
|
| 149 |
+
files.append({"path": relative, "size": size, "sha256": digest})
|
| 150 |
+
|
| 151 |
+
declared_images = set(samples_by_image)
|
| 152 |
+
actual_images = {path.relative_to(eval_dir).as_posix() for path in image_paths}
|
| 153 |
+
extra = sorted(declared_images - actual_images)
|
| 154 |
+
if extra:
|
| 155 |
+
raise FileNotFoundError("Manifest references missing images: " + ", ".join(extra))
|
| 156 |
+
|
| 157 |
+
content_digest.update(manifest_path.read_bytes())
|
| 158 |
+
return {
|
| 159 |
+
"name": eval_dir.name,
|
| 160 |
+
"num_samples": len(image_paths),
|
| 161 |
+
"num_digit_samples": digit_samples,
|
| 162 |
+
"num_cjk_samples": cjk_samples,
|
| 163 |
+
"num_table_samples": table_samples,
|
| 164 |
+
"category_counts": dict(sorted(category_counts.items())),
|
| 165 |
+
"manifest_sha256": sha256_file(manifest_path),
|
| 166 |
+
"content_sha256": content_digest.hexdigest(),
|
| 167 |
+
"files": files,
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def _pattern_is_within(child: str, parent: str) -> bool:
|
| 172 |
+
"""Return whether a precision rule is the parent itself or a descendant."""
|
| 173 |
+
return child == parent or child.startswith(parent + ".")
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def generate_precision_map(
|
| 177 |
+
base_map: dict,
|
| 178 |
+
sensitivity_results: dict,
|
| 179 |
+
*,
|
| 180 |
+
calibration_results: dict | None = None,
|
| 181 |
+
cer_threshold: float = 0.02,
|
| 182 |
+
digit_cer_threshold: float = 0.02,
|
| 183 |
+
table_degradation_threshold: float = 0.01,
|
| 184 |
+
) -> dict:
|
| 185 |
+
"""Turn group sensitivity measurements into executable top-level rules."""
|
| 186 |
+
from quantization.layer_sensitivity import LAYER_GROUPS
|
| 187 |
+
|
| 188 |
+
groups = sensitivity_results.get("layer_groups")
|
| 189 |
+
if not isinstance(groups, dict) or not groups:
|
| 190 |
+
raise ValueError("Sensitivity results contain no layer_groups")
|
| 191 |
+
generated = {
|
| 192 |
+
pattern: precision
|
| 193 |
+
for pattern, precision in base_map.items()
|
| 194 |
+
if not pattern.startswith("_")
|
| 195 |
+
}
|
| 196 |
+
decisions = {}
|
| 197 |
+
for group_name, patterns in LAYER_GROUPS.items():
|
| 198 |
+
result = groups.get(group_name)
|
| 199 |
+
if not isinstance(result, dict):
|
| 200 |
+
raise ValueError(f"Sensitivity result missing group: {group_name}")
|
| 201 |
+
reasons = []
|
| 202 |
+
if result.get("status") != "success":
|
| 203 |
+
reasons.append(f"status={result.get('status', 'missing')}")
|
| 204 |
+
for key, threshold in (
|
| 205 |
+
("cer_delta", cer_threshold),
|
| 206 |
+
("digit_cer_delta", digit_cer_threshold),
|
| 207 |
+
("table_score_degradation", table_degradation_threshold),
|
| 208 |
+
):
|
| 209 |
+
value = result.get(key)
|
| 210 |
+
if isinstance(value, (int, float)) and not isinstance(value, bool) and value > threshold:
|
| 211 |
+
reasons.append(f"{key}={value:.6f}")
|
| 212 |
+
measured_values = [
|
| 213 |
+
result.get("cer_delta"),
|
| 214 |
+
result.get("digit_cer_delta"),
|
| 215 |
+
result.get("table_score_degradation"),
|
| 216 |
+
]
|
| 217 |
+
explicit_non_degradation = (
|
| 218 |
+
result.get("status") == "success"
|
| 219 |
+
and all(
|
| 220 |
+
value is None
|
| 221 |
+
or (
|
| 222 |
+
isinstance(value, (int, float))
|
| 223 |
+
and not isinstance(value, bool)
|
| 224 |
+
and value <= 0
|
| 225 |
+
)
|
| 226 |
+
for value in measured_values
|
| 227 |
+
)
|
| 228 |
+
)
|
| 229 |
+
if group_name in HARD_PROTECTED_GROUPS:
|
| 230 |
+
reasons.append("protected OCR-critical group")
|
| 231 |
+
elif group_name in EVIDENCE_PROTECTED_GROUPS and not explicit_non_degradation:
|
| 232 |
+
reasons.append("protected unless all measured quality deltas are non-degrading")
|
| 233 |
+
|
| 234 |
+
retain_bf16 = bool(reasons)
|
| 235 |
+
if retain_bf16:
|
| 236 |
+
for group_pattern in patterns:
|
| 237 |
+
for existing_pattern in list(generated):
|
| 238 |
+
if _pattern_is_within(existing_pattern, group_pattern):
|
| 239 |
+
generated[existing_pattern] = "bfloat16"
|
| 240 |
+
generated[group_pattern] = "bfloat16"
|
| 241 |
+
elif group_name in EVIDENCE_PROTECTED_GROUPS and explicit_non_degradation:
|
| 242 |
+
for group_pattern in patterns:
|
| 243 |
+
for existing_pattern in list(generated):
|
| 244 |
+
if _pattern_is_within(existing_pattern, group_pattern):
|
| 245 |
+
generated[existing_pattern] = "mxfp8"
|
| 246 |
+
generated[group_pattern] = "mxfp8"
|
| 247 |
+
decisions[group_name] = {
|
| 248 |
+
"precision": (
|
| 249 |
+
"bfloat16" if retain_bf16
|
| 250 |
+
else "mxfp8" if group_name in EVIDENCE_PROTECTED_GROUPS
|
| 251 |
+
else "base-map"
|
| 252 |
+
),
|
| 253 |
+
"reasons": reasons or (
|
| 254 |
+
["all measured quality deltas were non-degrading"]
|
| 255 |
+
if group_name in EVIDENCE_PROTECTED_GROUPS
|
| 256 |
+
else ["within thresholds"]
|
| 257 |
+
),
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
calibration_summary = None
|
| 261 |
+
if calibration_results is not None:
|
| 262 |
+
overrides = calibration_results.get("precision_overrides")
|
| 263 |
+
if not isinstance(overrides, dict) or not overrides:
|
| 264 |
+
raise ValueError("Calibration results contain no precision_overrides")
|
| 265 |
+
allowed_precisions = {"bfloat16", "mxfp8", "affine8"}
|
| 266 |
+
for pattern, precision in overrides.items():
|
| 267 |
+
if not isinstance(pattern, str) or precision not in allowed_precisions:
|
| 268 |
+
raise ValueError(f"Invalid calibrated precision override: {pattern}={precision}")
|
| 269 |
+
generated[pattern] = precision
|
| 270 |
+
for group_name, patterns in LAYER_GROUPS.items():
|
| 271 |
+
if pattern in patterns:
|
| 272 |
+
decisions[group_name] = {
|
| 273 |
+
"precision": precision,
|
| 274 |
+
"reasons": [
|
| 275 |
+
"selected by joint quality/throughput calibration: "
|
| 276 |
+
+ str(calibration_results.get("selected", {}).get("label"))
|
| 277 |
+
],
|
| 278 |
+
}
|
| 279 |
+
calibration_summary = {
|
| 280 |
+
"selected": calibration_results.get("selected"),
|
| 281 |
+
"selection_policy": calibration_results.get("selection_policy"),
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
generated["_generated_from"] = {
|
| 285 |
+
"source_model": sensitivity_results.get("model_path"),
|
| 286 |
+
"thresholds": {
|
| 287 |
+
"cer_delta": cer_threshold,
|
| 288 |
+
"digit_cer_delta": digit_cer_threshold,
|
| 289 |
+
"table_score_degradation": table_degradation_threshold,
|
| 290 |
+
},
|
| 291 |
+
"decisions": decisions,
|
| 292 |
+
"calibration": calibration_summary,
|
| 293 |
+
}
|
| 294 |
+
return generated
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
def validate_candidate_metadata(model_dir: str | Path) -> dict:
|
| 298 |
+
"""Validate native Unlimited-OCR MXFP8 metadata."""
|
| 299 |
+
model_dir = Path(model_dir)
|
| 300 |
+
config = load_json_object(model_dir / "config.json")
|
| 301 |
+
processor = load_json_object(model_dir / "processor_config.json")
|
| 302 |
+
architectures = config.get("architectures")
|
| 303 |
+
quantization = config.get("quantization") or config.get("quantization_config")
|
| 304 |
+
text_config = config.get("text_config") or config.get("language_config") or config
|
| 305 |
+
window = text_config.get("sliding_window_size") if isinstance(text_config, dict) else None
|
| 306 |
+
if window is None and isinstance(text_config, dict):
|
| 307 |
+
window = text_config.get("sliding_window")
|
| 308 |
+
checks = {
|
| 309 |
+
"architecture": isinstance(architectures, list) and "UnlimitedOCRForCausalLM" in architectures,
|
| 310 |
+
"model_type": config.get("model_type") == "unlimited-ocr",
|
| 311 |
+
"mxfp8": isinstance(quantization, dict) and quantization.get("mode") == "mxfp8",
|
| 312 |
+
"sliding_window": isinstance(window, int) and not isinstance(window, bool) and window > 0,
|
| 313 |
+
"processor_class": processor.get("processor_class") == "UnlimitedOCRHFProcessor",
|
| 314 |
+
"sft_format": processor.get("sft_format") == "unlimitedocr",
|
| 315 |
+
}
|
| 316 |
+
return {"passed": all(checks.values()), "checks": checks, "sliding_window": window}
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def _number(payload: dict, key: str) -> float | None:
|
| 320 |
+
value = payload.get(key)
|
| 321 |
+
return float(value) if isinstance(value, (int, float)) and not isinstance(value, bool) else None
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def _gate(name: str, actual: Any, limit: Any, passed: bool, detail: str) -> dict:
|
| 325 |
+
return {"name": name, "passed": bool(passed), "actual": actual, "limit": limit, "detail": detail}
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
def evaluate_release_gates(
|
| 329 |
+
*,
|
| 330 |
+
candidate_weights: dict,
|
| 331 |
+
reference_weights: dict,
|
| 332 |
+
dataset: dict,
|
| 333 |
+
metadata: dict,
|
| 334 |
+
bf16_accuracy: dict,
|
| 335 |
+
reference_accuracy: dict,
|
| 336 |
+
candidate_accuracy: dict,
|
| 337 |
+
reference_performance: dict,
|
| 338 |
+
candidate_performance: dict,
|
| 339 |
+
rswa: dict,
|
| 340 |
+
thresholds: dict | None = None,
|
| 341 |
+
) -> list[dict]:
|
| 342 |
+
"""Evaluate every required release gate; missing values fail closed."""
|
| 343 |
+
limits = {**DEFAULT_THRESHOLDS, **(thresholds or {})}
|
| 344 |
+
gates = []
|
| 345 |
+
candidate_digest = candidate_weights.get("aggregate_sha256")
|
| 346 |
+
reference_digest = reference_weights.get("aggregate_sha256")
|
| 347 |
+
gates.append(_gate(
|
| 348 |
+
"weights_are_distinct", candidate_digest, f"different from {reference_digest}",
|
| 349 |
+
bool(candidate_digest and reference_digest and candidate_digest != reference_digest),
|
| 350 |
+
"Candidate aggregate digest must differ from the Sahil reference",
|
| 351 |
+
))
|
| 352 |
+
candidate_size = _number(candidate_weights, "total_size_gb")
|
| 353 |
+
gates.append(_gate(
|
| 354 |
+
"weight_size_gb", candidate_size, limits["max_weight_size_gb"],
|
| 355 |
+
candidate_size is not None and candidate_size <= limits["max_weight_size_gb"],
|
| 356 |
+
"Candidate Safetensors size",
|
| 357 |
+
))
|
| 358 |
+
gates.append(_gate(
|
| 359 |
+
"native_model_metadata", metadata.get("checks"), True,
|
| 360 |
+
metadata.get("passed") is True, "Native Unlimited-OCR, MXFP8, and R-SWA metadata",
|
| 361 |
+
))
|
| 362 |
+
|
| 363 |
+
coverage = {
|
| 364 |
+
"samples": dataset.get("num_samples", 0),
|
| 365 |
+
"digit": dataset.get("num_digit_samples", 0),
|
| 366 |
+
"cjk": dataset.get("num_cjk_samples", 0),
|
| 367 |
+
"table": dataset.get("num_table_samples", 0),
|
| 368 |
+
}
|
| 369 |
+
gates.append(_gate(
|
| 370 |
+
"evaluation_coverage", coverage, "all counts > 0",
|
| 371 |
+
all(isinstance(value, int) and value > 0 for value in coverage.values()),
|
| 372 |
+
"Dataset must cover ordinary text, digits, CJK, and tables",
|
| 373 |
+
))
|
| 374 |
+
sample_counts = [
|
| 375 |
+
bf16_accuracy.get("num_samples"),
|
| 376 |
+
reference_accuracy.get("num_samples"),
|
| 377 |
+
candidate_accuracy.get("num_samples"),
|
| 378 |
+
]
|
| 379 |
+
gates.append(_gate(
|
| 380 |
+
"same_evaluation_samples", sample_counts, dataset.get("num_samples"),
|
| 381 |
+
all(count == dataset.get("num_samples") for count in sample_counts),
|
| 382 |
+
"All three checkpoints must run the complete evaluation set",
|
| 383 |
+
))
|
| 384 |
+
|
| 385 |
+
bf16_cer = _number(bf16_accuracy, "mean_cer")
|
| 386 |
+
reference_cer = _number(reference_accuracy, "mean_cer")
|
| 387 |
+
candidate_cer = _number(candidate_accuracy, "mean_cer")
|
| 388 |
+
delta_bf16 = candidate_cer - bf16_cer if candidate_cer is not None and bf16_cer is not None else None
|
| 389 |
+
delta_reference = candidate_cer - reference_cer if candidate_cer is not None and reference_cer is not None else None
|
| 390 |
+
gates.append(_gate(
|
| 391 |
+
"candidate_cer_vs_bf16", delta_bf16, limits["max_cer_delta_vs_bf16"],
|
| 392 |
+
delta_bf16 is not None and delta_bf16 <= limits["max_cer_delta_vs_bf16"],
|
| 393 |
+
"Candidate minus BF16 absolute mean CER",
|
| 394 |
+
))
|
| 395 |
+
gates.append(_gate(
|
| 396 |
+
"candidate_cer_vs_reference", delta_reference, limits["max_cer_delta_vs_reference"],
|
| 397 |
+
delta_reference is not None and delta_reference <= limits["max_cer_delta_vs_reference"],
|
| 398 |
+
"Candidate minus Sahil-reference absolute mean CER",
|
| 399 |
+
))
|
| 400 |
+
|
| 401 |
+
bf16_digit = _number(bf16_accuracy, "mean_digit_cer")
|
| 402 |
+
candidate_digit = _number(candidate_accuracy, "mean_digit_cer")
|
| 403 |
+
digit_delta = candidate_digit - bf16_digit if candidate_digit is not None and bf16_digit is not None else None
|
| 404 |
+
gates.append(_gate(
|
| 405 |
+
"candidate_digit_cer_vs_bf16", digit_delta, limits["max_digit_cer_delta_vs_bf16"],
|
| 406 |
+
digit_delta is not None and digit_delta <= limits["max_digit_cer_delta_vs_bf16"],
|
| 407 |
+
"Candidate minus BF16 digit CER",
|
| 408 |
+
))
|
| 409 |
+
|
| 410 |
+
bf16_table = _number(bf16_accuracy, "mean_table_score")
|
| 411 |
+
candidate_table = _number(candidate_accuracy, "mean_table_score")
|
| 412 |
+
table_degradation = bf16_table - candidate_table if bf16_table is not None and candidate_table is not None else None
|
| 413 |
+
gates.append(_gate(
|
| 414 |
+
"candidate_table_score_vs_bf16", table_degradation,
|
| 415 |
+
limits["max_table_score_degradation_vs_bf16"],
|
| 416 |
+
table_degradation is not None and table_degradation <= limits["max_table_score_degradation_vs_bf16"],
|
| 417 |
+
"BF16 minus candidate mean table score",
|
| 418 |
+
))
|
| 419 |
+
|
| 420 |
+
reference_tps = _number(reference_performance, "mean_tps")
|
| 421 |
+
candidate_tps = _number(candidate_performance, "mean_tps")
|
| 422 |
+
tps_ratio = candidate_tps / reference_tps if candidate_tps is not None and reference_tps and reference_tps > 0 else None
|
| 423 |
+
gates.append(_gate(
|
| 424 |
+
"candidate_tps_vs_reference", tps_ratio, limits["min_tps_ratio_vs_reference"],
|
| 425 |
+
tps_ratio is not None and tps_ratio >= limits["min_tps_ratio_vs_reference"],
|
| 426 |
+
"Candidate decode throughput divided by Sahil-reference throughput",
|
| 427 |
+
))
|
| 428 |
+
gates.append(_gate(
|
| 429 |
+
"rswa_8k_bounded", rswa.get("pass_conditions"), True,
|
| 430 |
+
rswa.get("passed") is True, "8K generation, bounded cache, and stable throughput",
|
| 431 |
+
))
|
| 432 |
+
return gates
|
| 433 |
+
|
| 434 |
+
|
| 435 |
+
def _metric_summary(payload: dict) -> dict:
|
| 436 |
+
keys = (
|
| 437 |
+
"model_path", "num_samples", "mean_cer", "mean_digit_cer",
|
| 438 |
+
"mean_cjk_cer", "mean_table_score", "mean_tps",
|
| 439 |
+
"mean_peak_memory_mb", "max_tokens", "profile",
|
| 440 |
+
)
|
| 441 |
+
return {key: payload.get(key) for key in keys if key in payload}
|
| 442 |
+
|
| 443 |
+
|
| 444 |
+
def _runtime_versions() -> dict:
|
| 445 |
+
versions = {}
|
| 446 |
+
for distribution in ("mlx", "mlx-vlm", "huggingface-hub", "numpy", "Pillow"):
|
| 447 |
+
try:
|
| 448 |
+
versions[distribution] = importlib.metadata.version(distribution)
|
| 449 |
+
except importlib.metadata.PackageNotFoundError:
|
| 450 |
+
versions[distribution] = None
|
| 451 |
+
return versions
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
def build_release_manifest(
|
| 455 |
+
*,
|
| 456 |
+
candidate_dir: str | Path,
|
| 457 |
+
reference_dir: str | Path,
|
| 458 |
+
source_dir: str | Path,
|
| 459 |
+
eval_dir: str | Path,
|
| 460 |
+
evidence_paths: dict[str, str | Path],
|
| 461 |
+
repo_id: str,
|
| 462 |
+
source_id: str,
|
| 463 |
+
reference_id: str,
|
| 464 |
+
thresholds: dict | None = None,
|
| 465 |
+
) -> dict:
|
| 466 |
+
"""Build a complete release decision from on-disk evidence."""
|
| 467 |
+
evidence = {name: load_json_object(path) for name, path in evidence_paths.items()}
|
| 468 |
+
required = {
|
| 469 |
+
"bf16_accuracy", "reference_accuracy", "candidate_accuracy",
|
| 470 |
+
"reference_performance", "candidate_performance", "candidate_rswa",
|
| 471 |
+
"sensitivity_results", "calibration_results", "generated_precision_map", "provenance",
|
| 472 |
+
}
|
| 473 |
+
missing = sorted(required - set(evidence))
|
| 474 |
+
if missing:
|
| 475 |
+
raise ValueError("Missing release evidence: " + ", ".join(missing))
|
| 476 |
+
|
| 477 |
+
candidate_weights = model_weight_manifest(candidate_dir)
|
| 478 |
+
reference_weights = model_weight_manifest(reference_dir)
|
| 479 |
+
source_weights = model_weight_manifest(source_dir)
|
| 480 |
+
dataset = dataset_manifest(eval_dir)
|
| 481 |
+
metadata = validate_candidate_metadata(candidate_dir)
|
| 482 |
+
gates = evaluate_release_gates(
|
| 483 |
+
candidate_weights=candidate_weights,
|
| 484 |
+
reference_weights=reference_weights,
|
| 485 |
+
dataset=dataset,
|
| 486 |
+
metadata=metadata,
|
| 487 |
+
bf16_accuracy=evidence["bf16_accuracy"],
|
| 488 |
+
reference_accuracy=evidence["reference_accuracy"],
|
| 489 |
+
candidate_accuracy=evidence["candidate_accuracy"],
|
| 490 |
+
reference_performance=evidence["reference_performance"],
|
| 491 |
+
candidate_performance=evidence["candidate_performance"],
|
| 492 |
+
rswa=evidence["candidate_rswa"],
|
| 493 |
+
thresholds=thresholds,
|
| 494 |
+
)
|
| 495 |
+
artifact_hashes = {
|
| 496 |
+
name: {
|
| 497 |
+
"filename": Path(path).name,
|
| 498 |
+
"size": Path(path).stat().st_size,
|
| 499 |
+
"sha256": sha256_file(path),
|
| 500 |
+
}
|
| 501 |
+
for name, path in evidence_paths.items()
|
| 502 |
+
}
|
| 503 |
+
limits = {**DEFAULT_THRESHOLDS, **(thresholds or {})}
|
| 504 |
+
return {
|
| 505 |
+
"schema_version": 1,
|
| 506 |
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
| 507 |
+
"release_approved": all(gate["passed"] for gate in gates),
|
| 508 |
+
"repo_id": repo_id,
|
| 509 |
+
"source": {"id": source_id, "weights": source_weights},
|
| 510 |
+
"reference": {"id": reference_id, "weights": reference_weights},
|
| 511 |
+
"candidate": {
|
| 512 |
+
"name": Path(candidate_dir).name,
|
| 513 |
+
"weights": candidate_weights,
|
| 514 |
+
"metadata": metadata,
|
| 515 |
+
},
|
| 516 |
+
"dataset": dataset,
|
| 517 |
+
"thresholds": limits,
|
| 518 |
+
"metrics": {
|
| 519 |
+
name: _metric_summary(evidence[name])
|
| 520 |
+
for name in (
|
| 521 |
+
"bf16_accuracy", "reference_accuracy", "candidate_accuracy",
|
| 522 |
+
"reference_performance", "candidate_performance",
|
| 523 |
+
)
|
| 524 |
+
},
|
| 525 |
+
"rswa": evidence["candidate_rswa"],
|
| 526 |
+
"sensitivity": {
|
| 527 |
+
"baseline": evidence["sensitivity_results"].get("baseline"),
|
| 528 |
+
"groups": evidence["sensitivity_results"].get("layer_groups"),
|
| 529 |
+
},
|
| 530 |
+
"calibration": evidence["calibration_results"],
|
| 531 |
+
"precision_map": evidence["generated_precision_map"],
|
| 532 |
+
"artifacts": artifact_hashes,
|
| 533 |
+
"gates": gates,
|
| 534 |
+
"environment": {
|
| 535 |
+
"platform": platform.platform(),
|
| 536 |
+
"machine": platform.machine(),
|
| 537 |
+
"python": platform.python_version(),
|
| 538 |
+
"versions": _runtime_versions(),
|
| 539 |
+
},
|
| 540 |
+
}
|
| 541 |
+
|
| 542 |
+
|
| 543 |
+
def main() -> None:
|
| 544 |
+
parser = argparse.ArgumentParser(description="Build a fail-closed release manifest")
|
| 545 |
+
parser.add_argument("--candidate-dir", required=True, type=Path)
|
| 546 |
+
parser.add_argument("--reference-dir", required=True, type=Path)
|
| 547 |
+
parser.add_argument("--source-dir", required=True, type=Path)
|
| 548 |
+
parser.add_argument("--eval-dir", required=True, type=Path)
|
| 549 |
+
parser.add_argument("--artifacts-dir", required=True, type=Path)
|
| 550 |
+
parser.add_argument("--output", required=True, type=Path)
|
| 551 |
+
parser.add_argument("--repo-id", required=True)
|
| 552 |
+
parser.add_argument("--source-id", default="baidu/Unlimited-OCR")
|
| 553 |
+
parser.add_argument("--reference-id", default="sahilchachra/unlimited-ocr-mxfp8-mlx")
|
| 554 |
+
args = parser.parse_args()
|
| 555 |
+
|
| 556 |
+
evidence_paths = {
|
| 557 |
+
name: args.artifacts_dir / f"{name}.json"
|
| 558 |
+
for name in (
|
| 559 |
+
"bf16_accuracy", "reference_accuracy", "candidate_accuracy",
|
| 560 |
+
"reference_performance", "candidate_performance", "candidate_rswa",
|
| 561 |
+
"sensitivity_results", "calibration_results", "generated_precision_map", "provenance",
|
| 562 |
+
)
|
| 563 |
+
}
|
| 564 |
+
manifest = build_release_manifest(
|
| 565 |
+
candidate_dir=args.candidate_dir,
|
| 566 |
+
reference_dir=args.reference_dir,
|
| 567 |
+
source_dir=args.source_dir,
|
| 568 |
+
eval_dir=args.eval_dir,
|
| 569 |
+
evidence_paths=evidence_paths,
|
| 570 |
+
repo_id=args.repo_id,
|
| 571 |
+
source_id=args.source_id,
|
| 572 |
+
reference_id=args.reference_id,
|
| 573 |
+
)
|
| 574 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 575 |
+
args.output.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
| 576 |
+
for gate in manifest["gates"]:
|
| 577 |
+
print(f"[{'PASS' if gate['passed'] else 'FAIL'}] {gate['name']}: {gate['actual']}")
|
| 578 |
+
print(f"Release approved: {manifest['release_approved']}")
|
| 579 |
+
if not manifest["release_approved"]:
|
| 580 |
+
raise SystemExit(1)
|
| 581 |
+
|
| 582 |
+
|
| 583 |
+
if __name__ == "__main__":
|
| 584 |
+
main()
|
quantization/run_pipeline.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Orchestrate verified OCR-aware quantization, validation, and publication."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import importlib.metadata
|
| 8 |
+
import json
|
| 9 |
+
import platform
|
| 10 |
+
import subprocess
|
| 11 |
+
import sys
|
| 12 |
+
import time
|
| 13 |
+
from datetime import datetime, timezone
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
QUANT_DIR = Path(__file__).resolve().parent
|
| 18 |
+
PROJECT_ROOT = QUANT_DIR.parent
|
| 19 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 20 |
+
sys.path.insert(0, str(PROJECT_ROOT / "src"))
|
| 21 |
+
|
| 22 |
+
from quantization.release_gate import ( # noqa: E402
|
| 23 |
+
dataset_manifest,
|
| 24 |
+
generate_precision_map,
|
| 25 |
+
load_json_object,
|
| 26 |
+
sha256_file,
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
DEFAULT_SOURCE = PROJECT_ROOT / "reference" / "Unlimited-OCR"
|
| 31 |
+
DEFAULT_SOURCE_ID = "baidu/Unlimited-OCR"
|
| 32 |
+
DEFAULT_REFERENCE = "sahilchachra/unlimited-ocr-mxfp8-mlx"
|
| 33 |
+
DEFAULT_OUTPUT = PROJECT_ROOT / "models" / "AX-Unlimited-OCR-3B-MoE-MLX-MXFP8"
|
| 34 |
+
DEFAULT_ARTIFACTS = PROJECT_ROOT / "artifacts" / "ocr-aware-v1"
|
| 35 |
+
DEFAULT_REPO = "AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8"
|
| 36 |
+
BASE_PRECISION_MAP = QUANT_DIR / "precision_map.json"
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def run_command(cmd: list[str], description: str, *, dry_run: bool = False) -> bool:
|
| 40 |
+
"""Run one subprocess and stop the pipeline on any non-zero result."""
|
| 41 |
+
print("\n" + "=" * 72)
|
| 42 |
+
print(f"STEP: {description}")
|
| 43 |
+
print("CMD: " + " ".join(str(part) for part in cmd))
|
| 44 |
+
print("=" * 72)
|
| 45 |
+
if dry_run:
|
| 46 |
+
print("[DRY RUN] Command not executed")
|
| 47 |
+
return True
|
| 48 |
+
started = time.perf_counter()
|
| 49 |
+
result = subprocess.run(cmd, cwd=str(PROJECT_ROOT))
|
| 50 |
+
elapsed = time.perf_counter() - started
|
| 51 |
+
print(f"[{'OK' if result.returncode == 0 else 'FAIL'}] {description} ({elapsed:.1f}s)")
|
| 52 |
+
return result.returncode == 0
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def _config_path(model_path: str) -> Path:
|
| 56 |
+
local = Path(model_path)
|
| 57 |
+
if local.is_dir():
|
| 58 |
+
return local / "config.json"
|
| 59 |
+
from huggingface_hub import hf_hub_download
|
| 60 |
+
return Path(hf_hub_download(model_path, filename="config.json"))
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def assert_unquantized_source(model_path: str) -> dict:
|
| 64 |
+
"""Reject an already quantized source before expensive work."""
|
| 65 |
+
config_path = _config_path(model_path)
|
| 66 |
+
config = load_json_object(config_path)
|
| 67 |
+
if config.get("quantization") or config.get("quantization_config"):
|
| 68 |
+
raise ValueError("Source is already quantized; use the upstream BF16 checkpoint")
|
| 69 |
+
architectures = config.get("architectures")
|
| 70 |
+
if not isinstance(architectures, list) or "UnlimitedOCRForCausalLM" not in architectures:
|
| 71 |
+
raise ValueError("Source is not an UnlimitedOCRForCausalLM checkpoint")
|
| 72 |
+
return {"path": config_path.name, "sha256": sha256_file(config_path)}
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def system_provenance() -> dict:
|
| 76 |
+
versions = {}
|
| 77 |
+
for distribution in ("mlx", "mlx-vlm", "huggingface-hub", "numpy", "Pillow"):
|
| 78 |
+
try:
|
| 79 |
+
versions[distribution] = importlib.metadata.version(distribution)
|
| 80 |
+
except importlib.metadata.PackageNotFoundError:
|
| 81 |
+
versions[distribution] = None
|
| 82 |
+
return {
|
| 83 |
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
| 84 |
+
"python": platform.python_version(),
|
| 85 |
+
"platform": platform.platform(),
|
| 86 |
+
"machine": platform.machine(),
|
| 87 |
+
"processor": platform.processor(),
|
| 88 |
+
"versions": versions,
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def preflight(args: argparse.Namespace) -> bool:
|
| 93 |
+
"""Validate source, dataset, and release inputs before Metal allocation."""
|
| 94 |
+
try:
|
| 95 |
+
source_config = assert_unquantized_source(args.model_path)
|
| 96 |
+
dataset = dataset_manifest(args.eval_dir)
|
| 97 |
+
if not args.image.is_file():
|
| 98 |
+
raise FileNotFoundError(f"Performance/R-SWA image not found: {args.image}")
|
| 99 |
+
if args.output_dir.exists() and args.step in {"all", "convert"}:
|
| 100 |
+
raise FileExistsError(f"Output model directory already exists: {args.output_dir}")
|
| 101 |
+
args.artifacts_dir.mkdir(parents=True, exist_ok=True)
|
| 102 |
+
provenance = {
|
| 103 |
+
**system_provenance(),
|
| 104 |
+
"source_model": args.source_id,
|
| 105 |
+
"source_local_name": Path(args.model_path).name,
|
| 106 |
+
"source_config": source_config,
|
| 107 |
+
"reference_model": args.reference_model,
|
| 108 |
+
"target_repo": args.repo_id,
|
| 109 |
+
"dataset": dataset,
|
| 110 |
+
"smoke_image": {
|
| 111 |
+
"name": args.image.name,
|
| 112 |
+
"sha256": sha256_file(args.image),
|
| 113 |
+
},
|
| 114 |
+
"parameters": {
|
| 115 |
+
"accuracy_tokens": args.accuracy_tokens,
|
| 116 |
+
"performance_tokens": args.performance_tokens,
|
| 117 |
+
"performance_warmup": args.performance_warmup,
|
| 118 |
+
"performance_runs": args.performance_runs,
|
| 119 |
+
"rswa_lengths": args.rswa_lengths,
|
| 120 |
+
},
|
| 121 |
+
}
|
| 122 |
+
(args.artifacts_dir / "provenance.json").write_text(
|
| 123 |
+
json.dumps(provenance, indent=2, ensure_ascii=False) + "\n",
|
| 124 |
+
encoding="utf-8",
|
| 125 |
+
)
|
| 126 |
+
print(f"[OK] Unquantized source: {args.model_path}")
|
| 127 |
+
print(f"[OK] Evaluation samples: {dataset['num_samples']}")
|
| 128 |
+
print(f"[OK] Dataset digest: {dataset['content_sha256']}")
|
| 129 |
+
return True
|
| 130 |
+
except Exception as exc:
|
| 131 |
+
print(f"[FAIL] Preflight: {exc}")
|
| 132 |
+
return False
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def sensitivity(args: argparse.Namespace) -> bool:
|
| 136 |
+
cmd = [
|
| 137 |
+
sys.executable,
|
| 138 |
+
str(QUANT_DIR / "layer_sensitivity.py"),
|
| 139 |
+
"--model-path", args.model_path,
|
| 140 |
+
"--source-id", args.source_id,
|
| 141 |
+
"--eval-dir", str(args.eval_dir),
|
| 142 |
+
"--output", str(args.artifacts_dir / "sensitivity_results.json"),
|
| 143 |
+
"--max-tokens", str(args.accuracy_tokens),
|
| 144 |
+
]
|
| 145 |
+
return run_command(cmd, "Layer sensitivity analysis", dry_run=args.dry_run)
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def precision_map(args: argparse.Namespace) -> bool:
|
| 149 |
+
sensitivity_path = args.artifacts_dir / "sensitivity_results.json"
|
| 150 |
+
output_path = args.artifacts_dir / "generated_precision_map.json"
|
| 151 |
+
if args.dry_run:
|
| 152 |
+
print(f"[DRY RUN] Generate {output_path} from {sensitivity_path}")
|
| 153 |
+
return True
|
| 154 |
+
try:
|
| 155 |
+
calibration_path = args.artifacts_dir / "calibration_results.json"
|
| 156 |
+
generated = generate_precision_map(
|
| 157 |
+
load_json_object(BASE_PRECISION_MAP),
|
| 158 |
+
load_json_object(sensitivity_path),
|
| 159 |
+
calibration_results=(
|
| 160 |
+
load_json_object(calibration_path)
|
| 161 |
+
if calibration_path.is_file()
|
| 162 |
+
else None
|
| 163 |
+
),
|
| 164 |
+
)
|
| 165 |
+
output_path.write_text(
|
| 166 |
+
json.dumps(generated, indent=2, ensure_ascii=False) + "\n",
|
| 167 |
+
encoding="utf-8",
|
| 168 |
+
)
|
| 169 |
+
print(f"[OK] Generated executable precision map: {output_path}")
|
| 170 |
+
return True
|
| 171 |
+
except Exception as exc:
|
| 172 |
+
print(f"[FAIL] Precision-map generation: {exc}")
|
| 173 |
+
return False
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def convert(args: argparse.Namespace) -> bool:
|
| 177 |
+
cmd = [
|
| 178 |
+
sys.executable,
|
| 179 |
+
str(QUANT_DIR / "mixed_precision_convert.py"),
|
| 180 |
+
"--model-path", args.model_path,
|
| 181 |
+
"--precision-map", str(args.artifacts_dir / "generated_precision_map.json"),
|
| 182 |
+
"--output-dir", str(args.output_dir),
|
| 183 |
+
]
|
| 184 |
+
return run_command(cmd, "BF16 to OCR-aware MXFP8 conversion", dry_run=args.dry_run)
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def _accuracy_command(model: str, output: Path, args: argparse.Namespace) -> list[str]:
|
| 188 |
+
return [
|
| 189 |
+
sys.executable,
|
| 190 |
+
str(PROJECT_ROOT / "benchmarks" / "run_accuracy.py"),
|
| 191 |
+
"--model-path", model,
|
| 192 |
+
"--eval-dir", str(args.eval_dir),
|
| 193 |
+
"--output", str(output),
|
| 194 |
+
"--max-tokens", str(args.accuracy_tokens),
|
| 195 |
+
"--profile", "accurate",
|
| 196 |
+
]
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def _performance_command(model: str, output: Path, args: argparse.Namespace) -> list[str]:
|
| 200 |
+
return [
|
| 201 |
+
sys.executable,
|
| 202 |
+
str(PROJECT_ROOT / "benchmarks" / "run_performance.py"),
|
| 203 |
+
"--model-path", model,
|
| 204 |
+
"--image", str(args.image),
|
| 205 |
+
"--output", str(output),
|
| 206 |
+
"--max-tokens", str(args.performance_tokens),
|
| 207 |
+
"--warmup", str(args.performance_warmup),
|
| 208 |
+
"--runs", str(args.performance_runs),
|
| 209 |
+
]
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def validate(args: argparse.Namespace) -> bool:
|
| 213 |
+
"""Benchmark BF16, Sahil reference, candidate, then stress candidate R-SWA."""
|
| 214 |
+
jobs = [
|
| 215 |
+
(_accuracy_command(args.model_path, args.artifacts_dir / "bf16_accuracy.json", args), "BF16 accuracy"),
|
| 216 |
+
(_accuracy_command(args.reference_model, args.artifacts_dir / "reference_accuracy.json", args), "Sahil-reference accuracy"),
|
| 217 |
+
(_accuracy_command(str(args.output_dir), args.artifacts_dir / "candidate_accuracy.json", args), "Candidate accuracy"),
|
| 218 |
+
(_performance_command(args.reference_model, args.artifacts_dir / "reference_performance.json", args), "Sahil-reference performance"),
|
| 219 |
+
(_performance_command(str(args.output_dir), args.artifacts_dir / "candidate_performance.json", args), "Candidate performance"),
|
| 220 |
+
([
|
| 221 |
+
sys.executable,
|
| 222 |
+
str(PROJECT_ROOT / "benchmarks" / "rswa_validation.py"),
|
| 223 |
+
"--model-path", str(args.output_dir),
|
| 224 |
+
"--image", str(args.image),
|
| 225 |
+
"--output", str(args.artifacts_dir / "candidate_rswa.json"),
|
| 226 |
+
"--lengths", *[str(length) for length in args.rswa_lengths],
|
| 227 |
+
"--force-min-tokens", str(max(args.rswa_lengths)),
|
| 228 |
+
], "Candidate R-SWA stress validation"),
|
| 229 |
+
]
|
| 230 |
+
return all(run_command(cmd, description, dry_run=args.dry_run) for cmd, description in jobs)
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def resolve_reference_dir(reference_model: str) -> Path:
|
| 234 |
+
local = Path(reference_model)
|
| 235 |
+
if local.is_dir():
|
| 236 |
+
return local
|
| 237 |
+
from huggingface_hub import snapshot_download
|
| 238 |
+
return Path(snapshot_download(
|
| 239 |
+
reference_model,
|
| 240 |
+
allow_patterns=[
|
| 241 |
+
"*.safetensors", "model.safetensors.index.json", "config.json",
|
| 242 |
+
"processor_config.json", "tokenizer*.json", "special_tokens_map.json",
|
| 243 |
+
"chat_template.jinja",
|
| 244 |
+
],
|
| 245 |
+
))
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def gate(args: argparse.Namespace) -> bool:
|
| 249 |
+
if args.dry_run:
|
| 250 |
+
reference_dir = Path("<resolved-reference>")
|
| 251 |
+
else:
|
| 252 |
+
try:
|
| 253 |
+
reference_dir = resolve_reference_dir(args.reference_model)
|
| 254 |
+
except Exception as exc:
|
| 255 |
+
print(f"[FAIL] Reference download: {exc}")
|
| 256 |
+
return False
|
| 257 |
+
cmd = [
|
| 258 |
+
sys.executable,
|
| 259 |
+
str(QUANT_DIR / "release_gate.py"),
|
| 260 |
+
"--candidate-dir", str(args.output_dir),
|
| 261 |
+
"--reference-dir", str(reference_dir),
|
| 262 |
+
"--source-dir", args.model_path,
|
| 263 |
+
"--eval-dir", str(args.eval_dir),
|
| 264 |
+
"--artifacts-dir", str(args.artifacts_dir),
|
| 265 |
+
"--output", str(args.artifacts_dir / "release_manifest.json"),
|
| 266 |
+
"--repo-id", args.repo_id,
|
| 267 |
+
"--source-id", args.source_id,
|
| 268 |
+
"--reference-id", args.reference_model,
|
| 269 |
+
]
|
| 270 |
+
return run_command(cmd, "Fail-closed release gate", dry_run=args.dry_run)
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def publish(args: argparse.Namespace) -> bool:
|
| 274 |
+
cmd = [
|
| 275 |
+
sys.executable,
|
| 276 |
+
str(PROJECT_ROOT / "scripts" / "publish_optimized_model.py"),
|
| 277 |
+
"--model-dir", str(args.output_dir),
|
| 278 |
+
"--manifest", str(args.artifacts_dir / "release_manifest.json"),
|
| 279 |
+
"--artifacts-dir", str(args.artifacts_dir),
|
| 280 |
+
"--repo-id", args.repo_id,
|
| 281 |
+
]
|
| 282 |
+
if args.dry_run:
|
| 283 |
+
cmd.append("--dry-run")
|
| 284 |
+
return run_command(cmd, "Publish approved candidate to Hugging Face")
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def parse_args() -> argparse.Namespace:
|
| 288 |
+
parser = argparse.ArgumentParser(description="Verified OCR-aware model release pipeline")
|
| 289 |
+
parser.add_argument("--model-path", default=str(DEFAULT_SOURCE))
|
| 290 |
+
parser.add_argument("--source-id", default=DEFAULT_SOURCE_ID)
|
| 291 |
+
parser.add_argument("--reference-model", default=DEFAULT_REFERENCE)
|
| 292 |
+
parser.add_argument("--eval-dir", type=Path, default=PROJECT_ROOT / "eval_dataset")
|
| 293 |
+
parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT)
|
| 294 |
+
parser.add_argument("--artifacts-dir", type=Path, default=DEFAULT_ARTIFACTS)
|
| 295 |
+
parser.add_argument("--image", type=Path, default=PROJECT_ROOT / "test_data" / "test_invoice.png")
|
| 296 |
+
parser.add_argument("--repo-id", default=DEFAULT_REPO)
|
| 297 |
+
parser.add_argument("--accuracy-tokens", type=int, default=2048)
|
| 298 |
+
parser.add_argument("--performance-tokens", type=int, default=256)
|
| 299 |
+
parser.add_argument("--performance-warmup", type=int, default=1)
|
| 300 |
+
parser.add_argument("--performance-runs", type=int, default=3)
|
| 301 |
+
parser.add_argument("--rswa-lengths", type=int, nargs="+", default=[512, 2048, 8192])
|
| 302 |
+
parser.add_argument(
|
| 303 |
+
"--step",
|
| 304 |
+
choices=["all", "preflight", "sensitivity", "precision-map", "convert", "validate", "gate", "publish"],
|
| 305 |
+
default="all",
|
| 306 |
+
)
|
| 307 |
+
parser.add_argument("--dry-run", action="store_true")
|
| 308 |
+
args = parser.parse_args()
|
| 309 |
+
positive = ("accuracy_tokens", "performance_tokens", "performance_runs")
|
| 310 |
+
for name in positive:
|
| 311 |
+
if getattr(args, name) < 1:
|
| 312 |
+
parser.error(f"--{name.replace('_', '-')} must be positive")
|
| 313 |
+
if args.performance_warmup < 0:
|
| 314 |
+
parser.error("--performance-warmup must be non-negative")
|
| 315 |
+
if len(args.rswa_lengths) < 3 or args.rswa_lengths != sorted(set(args.rswa_lengths)) or 8192 not in args.rswa_lengths:
|
| 316 |
+
parser.error("--rswa-lengths must be sorted, unique, include 8192, and contain at least three values")
|
| 317 |
+
return args
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
def main() -> None:
|
| 321 |
+
args = parse_args()
|
| 322 |
+
steps = {
|
| 323 |
+
"preflight": lambda: preflight(args),
|
| 324 |
+
"sensitivity": lambda: sensitivity(args),
|
| 325 |
+
"precision-map": lambda: precision_map(args),
|
| 326 |
+
"convert": lambda: convert(args),
|
| 327 |
+
"validate": lambda: validate(args),
|
| 328 |
+
"gate": lambda: gate(args),
|
| 329 |
+
"publish": lambda: publish(args),
|
| 330 |
+
}
|
| 331 |
+
selected = list(steps) if args.step == "all" else [args.step]
|
| 332 |
+
for step_name in selected:
|
| 333 |
+
if not steps[step_name]():
|
| 334 |
+
print(f"[ABORT] Pipeline stopped at: {step_name}")
|
| 335 |
+
raise SystemExit(1)
|
| 336 |
+
print("[DONE] Requested pipeline steps completed successfully")
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
if __name__ == "__main__":
|
| 340 |
+
main()
|
quantization_config.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"quantization_method": "mxfp8",
|
| 3 |
+
"description": "Block-float MX FP8 quantization (~9.19 effective bits/weight) applied to language tower linear weights. Vision encoder and embeddings remain in bfloat16.",
|
| 4 |
+
"base_model": "baidu/Unlimited-OCR",
|
| 5 |
+
"reference_quantization": "sahilchachra/unlimited-ocr-mxfp8-mlx",
|
| 6 |
+
"quantized_components": {
|
| 7 |
+
"language_model.linear": "mxfp8",
|
| 8 |
+
"language_model.moe.experts": "mxfp8"
|
| 9 |
+
},
|
| 10 |
+
"preserved_bf16_components": {
|
| 11 |
+
"vision_encoder": "bfloat16",
|
| 12 |
+
"vision_projector": "bfloat16",
|
| 13 |
+
"token_embeddings": "bfloat16",
|
| 14 |
+
"normalization_layers": "bfloat16",
|
| 15 |
+
"position_embeddings": "bfloat16"
|
| 16 |
+
},
|
| 17 |
+
"effective_bits_per_weight": 9.19,
|
| 18 |
+
"model_size_gb": 3.83,
|
| 19 |
+
"conversion_tool": "mlx-vlm quantizers",
|
| 20 |
+
"notes": [
|
| 21 |
+
"mlx-vlm quantizers only touch the language tower's linear weights",
|
| 22 |
+
"The vision encoder and embeddings remain bf16",
|
| 23 |
+
"On-disk size (~3.6 GB) averages both precision levels",
|
| 24 |
+
"Published config.json uses model_type 'unlimited-ocr' to select mlx-vlm's native R-SWA implementation"
|
| 25 |
+
]
|
| 26 |
+
}
|
quantization_summary.json
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"method": "mxfp8",
|
| 3 |
+
"group_size": 32,
|
| 4 |
+
"bits": 8,
|
| 5 |
+
"source_model": "baidu/Unlimited-OCR",
|
| 6 |
+
"precision_map_sha256": "2c0bfbd58fcbb22e17927f3c5e8214e4933a3675a6d1a87e8b3b87b4fbb37b33",
|
| 7 |
+
"quantized_module_count": 120,
|
| 8 |
+
"quantized_precision_counts": {
|
| 9 |
+
"affine8": 1,
|
| 10 |
+
"mxfp8": 119
|
| 11 |
+
},
|
| 12 |
+
"preserved_quantizable_module_count": 145,
|
| 13 |
+
"quantized_modules": [
|
| 14 |
+
"language_model.lm_head",
|
| 15 |
+
"language_model.model.embed_tokens",
|
| 16 |
+
"language_model.model.layers.0.mlp.down_proj",
|
| 17 |
+
"language_model.model.layers.0.mlp.gate_proj",
|
| 18 |
+
"language_model.model.layers.0.mlp.up_proj",
|
| 19 |
+
"language_model.model.layers.0.self_attn.k_proj",
|
| 20 |
+
"language_model.model.layers.0.self_attn.o_proj",
|
| 21 |
+
"language_model.model.layers.0.self_attn.q_proj",
|
| 22 |
+
"language_model.model.layers.0.self_attn.v_proj",
|
| 23 |
+
"language_model.model.layers.1.mlp.shared_experts.down_proj",
|
| 24 |
+
"language_model.model.layers.1.mlp.shared_experts.gate_proj",
|
| 25 |
+
"language_model.model.layers.1.mlp.shared_experts.up_proj",
|
| 26 |
+
"language_model.model.layers.1.mlp.switch_mlp.down_proj",
|
| 27 |
+
"language_model.model.layers.1.mlp.switch_mlp.gate_proj",
|
| 28 |
+
"language_model.model.layers.1.mlp.switch_mlp.up_proj",
|
| 29 |
+
"language_model.model.layers.1.self_attn.k_proj",
|
| 30 |
+
"language_model.model.layers.1.self_attn.o_proj",
|
| 31 |
+
"language_model.model.layers.1.self_attn.q_proj",
|
| 32 |
+
"language_model.model.layers.1.self_attn.v_proj",
|
| 33 |
+
"language_model.model.layers.10.mlp.shared_experts.down_proj",
|
| 34 |
+
"language_model.model.layers.10.mlp.shared_experts.gate_proj",
|
| 35 |
+
"language_model.model.layers.10.mlp.shared_experts.up_proj",
|
| 36 |
+
"language_model.model.layers.10.mlp.switch_mlp.down_proj",
|
| 37 |
+
"language_model.model.layers.10.mlp.switch_mlp.gate_proj",
|
| 38 |
+
"language_model.model.layers.10.mlp.switch_mlp.up_proj",
|
| 39 |
+
"language_model.model.layers.10.self_attn.k_proj",
|
| 40 |
+
"language_model.model.layers.10.self_attn.o_proj",
|
| 41 |
+
"language_model.model.layers.10.self_attn.q_proj",
|
| 42 |
+
"language_model.model.layers.10.self_attn.v_proj",
|
| 43 |
+
"language_model.model.layers.11.mlp.shared_experts.down_proj",
|
| 44 |
+
"language_model.model.layers.11.mlp.shared_experts.gate_proj",
|
| 45 |
+
"language_model.model.layers.11.mlp.shared_experts.up_proj",
|
| 46 |
+
"language_model.model.layers.11.mlp.switch_mlp.down_proj",
|
| 47 |
+
"language_model.model.layers.11.mlp.switch_mlp.gate_proj",
|
| 48 |
+
"language_model.model.layers.11.mlp.switch_mlp.up_proj",
|
| 49 |
+
"language_model.model.layers.11.self_attn.k_proj",
|
| 50 |
+
"language_model.model.layers.11.self_attn.o_proj",
|
| 51 |
+
"language_model.model.layers.11.self_attn.q_proj",
|
| 52 |
+
"language_model.model.layers.11.self_attn.v_proj",
|
| 53 |
+
"language_model.model.layers.2.mlp.shared_experts.down_proj",
|
| 54 |
+
"language_model.model.layers.2.mlp.shared_experts.gate_proj",
|
| 55 |
+
"language_model.model.layers.2.mlp.shared_experts.up_proj",
|
| 56 |
+
"language_model.model.layers.2.mlp.switch_mlp.down_proj",
|
| 57 |
+
"language_model.model.layers.2.mlp.switch_mlp.gate_proj",
|
| 58 |
+
"language_model.model.layers.2.mlp.switch_mlp.up_proj",
|
| 59 |
+
"language_model.model.layers.2.self_attn.k_proj",
|
| 60 |
+
"language_model.model.layers.2.self_attn.o_proj",
|
| 61 |
+
"language_model.model.layers.2.self_attn.q_proj",
|
| 62 |
+
"language_model.model.layers.2.self_attn.v_proj",
|
| 63 |
+
"language_model.model.layers.3.mlp.shared_experts.down_proj",
|
| 64 |
+
"language_model.model.layers.3.mlp.shared_experts.gate_proj",
|
| 65 |
+
"language_model.model.layers.3.mlp.shared_experts.up_proj",
|
| 66 |
+
"language_model.model.layers.3.mlp.switch_mlp.down_proj",
|
| 67 |
+
"language_model.model.layers.3.mlp.switch_mlp.gate_proj",
|
| 68 |
+
"language_model.model.layers.3.mlp.switch_mlp.up_proj",
|
| 69 |
+
"language_model.model.layers.3.self_attn.k_proj",
|
| 70 |
+
"language_model.model.layers.3.self_attn.o_proj",
|
| 71 |
+
"language_model.model.layers.3.self_attn.q_proj",
|
| 72 |
+
"language_model.model.layers.3.self_attn.v_proj",
|
| 73 |
+
"language_model.model.layers.4.mlp.shared_experts.down_proj",
|
| 74 |
+
"language_model.model.layers.4.mlp.shared_experts.gate_proj",
|
| 75 |
+
"language_model.model.layers.4.mlp.shared_experts.up_proj",
|
| 76 |
+
"language_model.model.layers.4.mlp.switch_mlp.down_proj",
|
| 77 |
+
"language_model.model.layers.4.mlp.switch_mlp.gate_proj",
|
| 78 |
+
"language_model.model.layers.4.mlp.switch_mlp.up_proj",
|
| 79 |
+
"language_model.model.layers.4.self_attn.k_proj",
|
| 80 |
+
"language_model.model.layers.4.self_attn.o_proj",
|
| 81 |
+
"language_model.model.layers.4.self_attn.q_proj",
|
| 82 |
+
"language_model.model.layers.4.self_attn.v_proj",
|
| 83 |
+
"language_model.model.layers.5.mlp.shared_experts.down_proj",
|
| 84 |
+
"language_model.model.layers.5.mlp.shared_experts.gate_proj",
|
| 85 |
+
"language_model.model.layers.5.mlp.shared_experts.up_proj",
|
| 86 |
+
"language_model.model.layers.5.mlp.switch_mlp.down_proj",
|
| 87 |
+
"language_model.model.layers.5.mlp.switch_mlp.gate_proj",
|
| 88 |
+
"language_model.model.layers.5.mlp.switch_mlp.up_proj",
|
| 89 |
+
"language_model.model.layers.5.self_attn.k_proj",
|
| 90 |
+
"language_model.model.layers.5.self_attn.o_proj",
|
| 91 |
+
"language_model.model.layers.5.self_attn.q_proj",
|
| 92 |
+
"language_model.model.layers.5.self_attn.v_proj",
|
| 93 |
+
"language_model.model.layers.6.mlp.shared_experts.down_proj",
|
| 94 |
+
"language_model.model.layers.6.mlp.shared_experts.gate_proj",
|
| 95 |
+
"language_model.model.layers.6.mlp.shared_experts.up_proj",
|
| 96 |
+
"language_model.model.layers.6.mlp.switch_mlp.down_proj",
|
| 97 |
+
"language_model.model.layers.6.mlp.switch_mlp.gate_proj",
|
| 98 |
+
"language_model.model.layers.6.mlp.switch_mlp.up_proj",
|
| 99 |
+
"language_model.model.layers.6.self_attn.k_proj",
|
| 100 |
+
"language_model.model.layers.6.self_attn.o_proj",
|
| 101 |
+
"language_model.model.layers.6.self_attn.q_proj",
|
| 102 |
+
"language_model.model.layers.6.self_attn.v_proj",
|
| 103 |
+
"language_model.model.layers.7.mlp.shared_experts.down_proj",
|
| 104 |
+
"language_model.model.layers.7.mlp.shared_experts.gate_proj",
|
| 105 |
+
"language_model.model.layers.7.mlp.shared_experts.up_proj",
|
| 106 |
+
"language_model.model.layers.7.mlp.switch_mlp.down_proj",
|
| 107 |
+
"language_model.model.layers.7.mlp.switch_mlp.gate_proj",
|
| 108 |
+
"language_model.model.layers.7.mlp.switch_mlp.up_proj",
|
| 109 |
+
"language_model.model.layers.7.self_attn.k_proj",
|
| 110 |
+
"language_model.model.layers.7.self_attn.o_proj",
|
| 111 |
+
"language_model.model.layers.7.self_attn.q_proj",
|
| 112 |
+
"language_model.model.layers.7.self_attn.v_proj",
|
| 113 |
+
"language_model.model.layers.8.mlp.shared_experts.down_proj",
|
| 114 |
+
"language_model.model.layers.8.mlp.shared_experts.gate_proj",
|
| 115 |
+
"language_model.model.layers.8.mlp.shared_experts.up_proj",
|
| 116 |
+
"language_model.model.layers.8.mlp.switch_mlp.down_proj",
|
| 117 |
+
"language_model.model.layers.8.mlp.switch_mlp.gate_proj",
|
| 118 |
+
"language_model.model.layers.8.mlp.switch_mlp.up_proj",
|
| 119 |
+
"language_model.model.layers.8.self_attn.k_proj",
|
| 120 |
+
"language_model.model.layers.8.self_attn.o_proj",
|
| 121 |
+
"language_model.model.layers.8.self_attn.q_proj",
|
| 122 |
+
"language_model.model.layers.8.self_attn.v_proj",
|
| 123 |
+
"language_model.model.layers.9.mlp.shared_experts.down_proj",
|
| 124 |
+
"language_model.model.layers.9.mlp.shared_experts.gate_proj",
|
| 125 |
+
"language_model.model.layers.9.mlp.shared_experts.up_proj",
|
| 126 |
+
"language_model.model.layers.9.mlp.switch_mlp.down_proj",
|
| 127 |
+
"language_model.model.layers.9.mlp.switch_mlp.gate_proj",
|
| 128 |
+
"language_model.model.layers.9.mlp.switch_mlp.up_proj",
|
| 129 |
+
"language_model.model.layers.9.self_attn.k_proj",
|
| 130 |
+
"language_model.model.layers.9.self_attn.o_proj",
|
| 131 |
+
"language_model.model.layers.9.self_attn.q_proj",
|
| 132 |
+
"language_model.model.layers.9.self_attn.v_proj",
|
| 133 |
+
"projector.layers"
|
| 134 |
+
],
|
| 135 |
+
"quantized_module_precisions": {
|
| 136 |
+
"language_model.lm_head": "affine8",
|
| 137 |
+
"language_model.model.embed_tokens": "mxfp8",
|
| 138 |
+
"language_model.model.layers.0.mlp.down_proj": "mxfp8",
|
| 139 |
+
"language_model.model.layers.0.mlp.gate_proj": "mxfp8",
|
| 140 |
+
"language_model.model.layers.0.mlp.up_proj": "mxfp8",
|
| 141 |
+
"language_model.model.layers.0.self_attn.k_proj": "mxfp8",
|
| 142 |
+
"language_model.model.layers.0.self_attn.o_proj": "mxfp8",
|
| 143 |
+
"language_model.model.layers.0.self_attn.q_proj": "mxfp8",
|
| 144 |
+
"language_model.model.layers.0.self_attn.v_proj": "mxfp8",
|
| 145 |
+
"language_model.model.layers.1.mlp.shared_experts.down_proj": "mxfp8",
|
| 146 |
+
"language_model.model.layers.1.mlp.shared_experts.gate_proj": "mxfp8",
|
| 147 |
+
"language_model.model.layers.1.mlp.shared_experts.up_proj": "mxfp8",
|
| 148 |
+
"language_model.model.layers.1.mlp.switch_mlp.down_proj": "mxfp8",
|
| 149 |
+
"language_model.model.layers.1.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 150 |
+
"language_model.model.layers.1.mlp.switch_mlp.up_proj": "mxfp8",
|
| 151 |
+
"language_model.model.layers.1.self_attn.k_proj": "mxfp8",
|
| 152 |
+
"language_model.model.layers.1.self_attn.o_proj": "mxfp8",
|
| 153 |
+
"language_model.model.layers.1.self_attn.q_proj": "mxfp8",
|
| 154 |
+
"language_model.model.layers.1.self_attn.v_proj": "mxfp8",
|
| 155 |
+
"language_model.model.layers.10.mlp.shared_experts.down_proj": "mxfp8",
|
| 156 |
+
"language_model.model.layers.10.mlp.shared_experts.gate_proj": "mxfp8",
|
| 157 |
+
"language_model.model.layers.10.mlp.shared_experts.up_proj": "mxfp8",
|
| 158 |
+
"language_model.model.layers.10.mlp.switch_mlp.down_proj": "mxfp8",
|
| 159 |
+
"language_model.model.layers.10.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 160 |
+
"language_model.model.layers.10.mlp.switch_mlp.up_proj": "mxfp8",
|
| 161 |
+
"language_model.model.layers.10.self_attn.k_proj": "mxfp8",
|
| 162 |
+
"language_model.model.layers.10.self_attn.o_proj": "mxfp8",
|
| 163 |
+
"language_model.model.layers.10.self_attn.q_proj": "mxfp8",
|
| 164 |
+
"language_model.model.layers.10.self_attn.v_proj": "mxfp8",
|
| 165 |
+
"language_model.model.layers.11.mlp.shared_experts.down_proj": "mxfp8",
|
| 166 |
+
"language_model.model.layers.11.mlp.shared_experts.gate_proj": "mxfp8",
|
| 167 |
+
"language_model.model.layers.11.mlp.shared_experts.up_proj": "mxfp8",
|
| 168 |
+
"language_model.model.layers.11.mlp.switch_mlp.down_proj": "mxfp8",
|
| 169 |
+
"language_model.model.layers.11.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 170 |
+
"language_model.model.layers.11.mlp.switch_mlp.up_proj": "mxfp8",
|
| 171 |
+
"language_model.model.layers.11.self_attn.k_proj": "mxfp8",
|
| 172 |
+
"language_model.model.layers.11.self_attn.o_proj": "mxfp8",
|
| 173 |
+
"language_model.model.layers.11.self_attn.q_proj": "mxfp8",
|
| 174 |
+
"language_model.model.layers.11.self_attn.v_proj": "mxfp8",
|
| 175 |
+
"language_model.model.layers.2.mlp.shared_experts.down_proj": "mxfp8",
|
| 176 |
+
"language_model.model.layers.2.mlp.shared_experts.gate_proj": "mxfp8",
|
| 177 |
+
"language_model.model.layers.2.mlp.shared_experts.up_proj": "mxfp8",
|
| 178 |
+
"language_model.model.layers.2.mlp.switch_mlp.down_proj": "mxfp8",
|
| 179 |
+
"language_model.model.layers.2.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 180 |
+
"language_model.model.layers.2.mlp.switch_mlp.up_proj": "mxfp8",
|
| 181 |
+
"language_model.model.layers.2.self_attn.k_proj": "mxfp8",
|
| 182 |
+
"language_model.model.layers.2.self_attn.o_proj": "mxfp8",
|
| 183 |
+
"language_model.model.layers.2.self_attn.q_proj": "mxfp8",
|
| 184 |
+
"language_model.model.layers.2.self_attn.v_proj": "mxfp8",
|
| 185 |
+
"language_model.model.layers.3.mlp.shared_experts.down_proj": "mxfp8",
|
| 186 |
+
"language_model.model.layers.3.mlp.shared_experts.gate_proj": "mxfp8",
|
| 187 |
+
"language_model.model.layers.3.mlp.shared_experts.up_proj": "mxfp8",
|
| 188 |
+
"language_model.model.layers.3.mlp.switch_mlp.down_proj": "mxfp8",
|
| 189 |
+
"language_model.model.layers.3.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 190 |
+
"language_model.model.layers.3.mlp.switch_mlp.up_proj": "mxfp8",
|
| 191 |
+
"language_model.model.layers.3.self_attn.k_proj": "mxfp8",
|
| 192 |
+
"language_model.model.layers.3.self_attn.o_proj": "mxfp8",
|
| 193 |
+
"language_model.model.layers.3.self_attn.q_proj": "mxfp8",
|
| 194 |
+
"language_model.model.layers.3.self_attn.v_proj": "mxfp8",
|
| 195 |
+
"language_model.model.layers.4.mlp.shared_experts.down_proj": "mxfp8",
|
| 196 |
+
"language_model.model.layers.4.mlp.shared_experts.gate_proj": "mxfp8",
|
| 197 |
+
"language_model.model.layers.4.mlp.shared_experts.up_proj": "mxfp8",
|
| 198 |
+
"language_model.model.layers.4.mlp.switch_mlp.down_proj": "mxfp8",
|
| 199 |
+
"language_model.model.layers.4.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 200 |
+
"language_model.model.layers.4.mlp.switch_mlp.up_proj": "mxfp8",
|
| 201 |
+
"language_model.model.layers.4.self_attn.k_proj": "mxfp8",
|
| 202 |
+
"language_model.model.layers.4.self_attn.o_proj": "mxfp8",
|
| 203 |
+
"language_model.model.layers.4.self_attn.q_proj": "mxfp8",
|
| 204 |
+
"language_model.model.layers.4.self_attn.v_proj": "mxfp8",
|
| 205 |
+
"language_model.model.layers.5.mlp.shared_experts.down_proj": "mxfp8",
|
| 206 |
+
"language_model.model.layers.5.mlp.shared_experts.gate_proj": "mxfp8",
|
| 207 |
+
"language_model.model.layers.5.mlp.shared_experts.up_proj": "mxfp8",
|
| 208 |
+
"language_model.model.layers.5.mlp.switch_mlp.down_proj": "mxfp8",
|
| 209 |
+
"language_model.model.layers.5.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 210 |
+
"language_model.model.layers.5.mlp.switch_mlp.up_proj": "mxfp8",
|
| 211 |
+
"language_model.model.layers.5.self_attn.k_proj": "mxfp8",
|
| 212 |
+
"language_model.model.layers.5.self_attn.o_proj": "mxfp8",
|
| 213 |
+
"language_model.model.layers.5.self_attn.q_proj": "mxfp8",
|
| 214 |
+
"language_model.model.layers.5.self_attn.v_proj": "mxfp8",
|
| 215 |
+
"language_model.model.layers.6.mlp.shared_experts.down_proj": "mxfp8",
|
| 216 |
+
"language_model.model.layers.6.mlp.shared_experts.gate_proj": "mxfp8",
|
| 217 |
+
"language_model.model.layers.6.mlp.shared_experts.up_proj": "mxfp8",
|
| 218 |
+
"language_model.model.layers.6.mlp.switch_mlp.down_proj": "mxfp8",
|
| 219 |
+
"language_model.model.layers.6.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 220 |
+
"language_model.model.layers.6.mlp.switch_mlp.up_proj": "mxfp8",
|
| 221 |
+
"language_model.model.layers.6.self_attn.k_proj": "mxfp8",
|
| 222 |
+
"language_model.model.layers.6.self_attn.o_proj": "mxfp8",
|
| 223 |
+
"language_model.model.layers.6.self_attn.q_proj": "mxfp8",
|
| 224 |
+
"language_model.model.layers.6.self_attn.v_proj": "mxfp8",
|
| 225 |
+
"language_model.model.layers.7.mlp.shared_experts.down_proj": "mxfp8",
|
| 226 |
+
"language_model.model.layers.7.mlp.shared_experts.gate_proj": "mxfp8",
|
| 227 |
+
"language_model.model.layers.7.mlp.shared_experts.up_proj": "mxfp8",
|
| 228 |
+
"language_model.model.layers.7.mlp.switch_mlp.down_proj": "mxfp8",
|
| 229 |
+
"language_model.model.layers.7.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 230 |
+
"language_model.model.layers.7.mlp.switch_mlp.up_proj": "mxfp8",
|
| 231 |
+
"language_model.model.layers.7.self_attn.k_proj": "mxfp8",
|
| 232 |
+
"language_model.model.layers.7.self_attn.o_proj": "mxfp8",
|
| 233 |
+
"language_model.model.layers.7.self_attn.q_proj": "mxfp8",
|
| 234 |
+
"language_model.model.layers.7.self_attn.v_proj": "mxfp8",
|
| 235 |
+
"language_model.model.layers.8.mlp.shared_experts.down_proj": "mxfp8",
|
| 236 |
+
"language_model.model.layers.8.mlp.shared_experts.gate_proj": "mxfp8",
|
| 237 |
+
"language_model.model.layers.8.mlp.shared_experts.up_proj": "mxfp8",
|
| 238 |
+
"language_model.model.layers.8.mlp.switch_mlp.down_proj": "mxfp8",
|
| 239 |
+
"language_model.model.layers.8.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 240 |
+
"language_model.model.layers.8.mlp.switch_mlp.up_proj": "mxfp8",
|
| 241 |
+
"language_model.model.layers.8.self_attn.k_proj": "mxfp8",
|
| 242 |
+
"language_model.model.layers.8.self_attn.o_proj": "mxfp8",
|
| 243 |
+
"language_model.model.layers.8.self_attn.q_proj": "mxfp8",
|
| 244 |
+
"language_model.model.layers.8.self_attn.v_proj": "mxfp8",
|
| 245 |
+
"language_model.model.layers.9.mlp.shared_experts.down_proj": "mxfp8",
|
| 246 |
+
"language_model.model.layers.9.mlp.shared_experts.gate_proj": "mxfp8",
|
| 247 |
+
"language_model.model.layers.9.mlp.shared_experts.up_proj": "mxfp8",
|
| 248 |
+
"language_model.model.layers.9.mlp.switch_mlp.down_proj": "mxfp8",
|
| 249 |
+
"language_model.model.layers.9.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 250 |
+
"language_model.model.layers.9.mlp.switch_mlp.up_proj": "mxfp8",
|
| 251 |
+
"language_model.model.layers.9.self_attn.k_proj": "mxfp8",
|
| 252 |
+
"language_model.model.layers.9.self_attn.o_proj": "mxfp8",
|
| 253 |
+
"language_model.model.layers.9.self_attn.q_proj": "mxfp8",
|
| 254 |
+
"language_model.model.layers.9.self_attn.v_proj": "mxfp8",
|
| 255 |
+
"projector.layers": "mxfp8"
|
| 256 |
+
}
|
| 257 |
+
}
|
release/bf16_accuracy.json
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_path": "Unlimited-OCR",
|
| 3 |
+
"prompt": "<image>document parsing.",
|
| 4 |
+
"max_tokens": 1024,
|
| 5 |
+
"profile": "accurate",
|
| 6 |
+
"generation_settings": {
|
| 7 |
+
"temperature": 0.0,
|
| 8 |
+
"top_p": 1.0,
|
| 9 |
+
"repetition_penalty": 1.0,
|
| 10 |
+
"no_repeat_ngram_size": 35,
|
| 11 |
+
"ngram_window": 128
|
| 12 |
+
},
|
| 13 |
+
"num_images": 12,
|
| 14 |
+
"num_samples": 12,
|
| 15 |
+
"total_time_seconds": 36.51101154302887,
|
| 16 |
+
"mean_cer": 0.2391877132924964,
|
| 17 |
+
"mean_wer": 0.5784543844272946,
|
| 18 |
+
"num_digit_samples": 12,
|
| 19 |
+
"num_cjk_samples": 3,
|
| 20 |
+
"mean_digit_cer": 0.08733996757027829,
|
| 21 |
+
"mean_cjk_cer": 0.3333333333333333,
|
| 22 |
+
"num_table_samples": 3,
|
| 23 |
+
"mean_table_score": 1.0,
|
| 24 |
+
"mean_repetition_rate": 0.0,
|
| 25 |
+
"per_file": [
|
| 26 |
+
{
|
| 27 |
+
"file": "0001.png",
|
| 28 |
+
"cer": 0.02315227070347284,
|
| 29 |
+
"wer": 0.19282511210762332,
|
| 30 |
+
"digit_cer": 0.03488372093023256,
|
| 31 |
+
"cjk_cer": 0.0,
|
| 32 |
+
"repetition_rate": 0.0,
|
| 33 |
+
"table_score": null,
|
| 34 |
+
"elapsed_seconds": 4.554586291997111,
|
| 35 |
+
"ref_length": 1345,
|
| 36 |
+
"hyp_length": 1348,
|
| 37 |
+
"ref_digit_count": 86,
|
| 38 |
+
"ref_cjk_count": 0
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"file": "0002.png",
|
| 42 |
+
"cer": 0.9133034379671151,
|
| 43 |
+
"wer": 0.46107784431137727,
|
| 44 |
+
"digit_cer": 0.12631578947368421,
|
| 45 |
+
"cjk_cer": 0.0,
|
| 46 |
+
"repetition_rate": 0.0,
|
| 47 |
+
"table_score": null,
|
| 48 |
+
"elapsed_seconds": 6.1484525830019265,
|
| 49 |
+
"ref_length": 835,
|
| 50 |
+
"hyp_length": 1378,
|
| 51 |
+
"ref_digit_count": 95,
|
| 52 |
+
"ref_cjk_count": 0
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"file": "0003.png",
|
| 56 |
+
"cer": 0.3923145665773012,
|
| 57 |
+
"wer": 0.3317757009345794,
|
| 58 |
+
"digit_cer": 0.18518518518518517,
|
| 59 |
+
"cjk_cer": 0.0,
|
| 60 |
+
"repetition_rate": 0.0,
|
| 61 |
+
"table_score": null,
|
| 62 |
+
"elapsed_seconds": 4.549903999999515,
|
| 63 |
+
"ref_length": 1332,
|
| 64 |
+
"hyp_length": 1719,
|
| 65 |
+
"ref_digit_count": 27,
|
| 66 |
+
"ref_cjk_count": 0
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"file": "0004.png",
|
| 70 |
+
"cer": 0.37906137184115524,
|
| 71 |
+
"wer": 0.36486486486486486,
|
| 72 |
+
"digit_cer": 0.1016949152542373,
|
| 73 |
+
"cjk_cer": 0.0,
|
| 74 |
+
"repetition_rate": 0.0,
|
| 75 |
+
"table_score": null,
|
| 76 |
+
"elapsed_seconds": 4.64588491700124,
|
| 77 |
+
"ref_length": 1329,
|
| 78 |
+
"hyp_length": 1710,
|
| 79 |
+
"ref_digit_count": 59,
|
| 80 |
+
"ref_cjk_count": 0
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"file": "0005.png",
|
| 84 |
+
"cer": 0.17715617715617715,
|
| 85 |
+
"wer": 1.5,
|
| 86 |
+
"digit_cer": 0.2,
|
| 87 |
+
"cjk_cer": 0.0,
|
| 88 |
+
"repetition_rate": 0.0,
|
| 89 |
+
"table_score": 1.0,
|
| 90 |
+
"elapsed_seconds": 3.0224843750038417,
|
| 91 |
+
"ref_length": 436,
|
| 92 |
+
"hyp_length": 508,
|
| 93 |
+
"ref_digit_count": 70,
|
| 94 |
+
"ref_cjk_count": 0
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
"file": "0006.png",
|
| 98 |
+
"cer": 0.17715617715617715,
|
| 99 |
+
"wer": 1.5,
|
| 100 |
+
"digit_cer": 0.2,
|
| 101 |
+
"cjk_cer": 0.0,
|
| 102 |
+
"repetition_rate": 0.0,
|
| 103 |
+
"table_score": 1.0,
|
| 104 |
+
"elapsed_seconds": 2.906020250011352,
|
| 105 |
+
"ref_length": 436,
|
| 106 |
+
"hyp_length": 508,
|
| 107 |
+
"ref_digit_count": 70,
|
| 108 |
+
"ref_cjk_count": 0
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"file": "0007.png",
|
| 112 |
+
"cer": 0.17715617715617715,
|
| 113 |
+
"wer": 1.5,
|
| 114 |
+
"digit_cer": 0.2,
|
| 115 |
+
"cjk_cer": 0.0,
|
| 116 |
+
"repetition_rate": 0.0,
|
| 117 |
+
"table_score": 1.0,
|
| 118 |
+
"elapsed_seconds": 2.9044155000010505,
|
| 119 |
+
"ref_length": 436,
|
| 120 |
+
"hyp_length": 508,
|
| 121 |
+
"ref_digit_count": 70,
|
| 122 |
+
"ref_cjk_count": 0
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"file": "0008.png",
|
| 126 |
+
"cer": 0.0,
|
| 127 |
+
"wer": 0.0,
|
| 128 |
+
"digit_cer": 0.0,
|
| 129 |
+
"cjk_cer": 0.0,
|
| 130 |
+
"repetition_rate": 0.0,
|
| 131 |
+
"table_score": null,
|
| 132 |
+
"elapsed_seconds": 1.5957032919977792,
|
| 133 |
+
"ref_length": 92,
|
| 134 |
+
"hyp_length": 92,
|
| 135 |
+
"ref_digit_count": 26,
|
| 136 |
+
"ref_cjk_count": 41
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"file": "0009.png",
|
| 140 |
+
"cer": 0.0,
|
| 141 |
+
"wer": 0.0,
|
| 142 |
+
"digit_cer": 0.0,
|
| 143 |
+
"cjk_cer": 0.0,
|
| 144 |
+
"repetition_rate": 0.0,
|
| 145 |
+
"table_score": null,
|
| 146 |
+
"elapsed_seconds": 1.5902405830129283,
|
| 147 |
+
"ref_length": 93,
|
| 148 |
+
"hyp_length": 93,
|
| 149 |
+
"ref_digit_count": 25,
|
| 150 |
+
"ref_cjk_count": 42
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"file": "0010.png",
|
| 154 |
+
"cer": 0.6309523809523809,
|
| 155 |
+
"wer": 1.0909090909090908,
|
| 156 |
+
"digit_cer": 0.0,
|
| 157 |
+
"cjk_cer": 1.0,
|
| 158 |
+
"repetition_rate": 0.0,
|
| 159 |
+
"table_score": null,
|
| 160 |
+
"elapsed_seconds": 1.6077932920015883,
|
| 161 |
+
"ref_length": 105,
|
| 162 |
+
"hyp_length": 117,
|
| 163 |
+
"ref_digit_count": 25,
|
| 164 |
+
"ref_cjk_count": 44
|
| 165 |
+
},
|
| 166 |
+
{
|
| 167 |
+
"file": "0011.png",
|
| 168 |
+
"cer": 0.0,
|
| 169 |
+
"wer": 0.0,
|
| 170 |
+
"digit_cer": 0.0,
|
| 171 |
+
"cjk_cer": 0.0,
|
| 172 |
+
"repetition_rate": 0.0,
|
| 173 |
+
"table_score": null,
|
| 174 |
+
"elapsed_seconds": 1.4870185839972692,
|
| 175 |
+
"ref_length": 118,
|
| 176 |
+
"hyp_length": 118,
|
| 177 |
+
"ref_digit_count": 27,
|
| 178 |
+
"ref_cjk_count": 0
|
| 179 |
+
},
|
| 180 |
+
{
|
| 181 |
+
"file": "0012.png",
|
| 182 |
+
"cer": 0.0,
|
| 183 |
+
"wer": 0.0,
|
| 184 |
+
"digit_cer": 0.0,
|
| 185 |
+
"cjk_cer": 0.0,
|
| 186 |
+
"repetition_rate": 0.0,
|
| 187 |
+
"table_score": null,
|
| 188 |
+
"elapsed_seconds": 1.4985078750032699,
|
| 189 |
+
"ref_length": 118,
|
| 190 |
+
"hyp_length": 118,
|
| 191 |
+
"ref_digit_count": 27,
|
| 192 |
+
"ref_cjk_count": 0
|
| 193 |
+
}
|
| 194 |
+
]
|
| 195 |
+
}
|
release/calibration_results.json
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema_version": 1,
|
| 3 |
+
"created_at": "2026-07-20T23:25:55.306169+00:00",
|
| 4 |
+
"target_pattern": "language_model.lm_head",
|
| 5 |
+
"selection_policy": "fastest candidate passing existing quality and throughput limits",
|
| 6 |
+
"thresholds": {
|
| 7 |
+
"max_cer_delta_vs_bf16": 0.01,
|
| 8 |
+
"max_cer_delta_vs_reference": 0.005,
|
| 9 |
+
"max_digit_cer_delta_vs_bf16": 0.01,
|
| 10 |
+
"max_table_score_degradation_vs_bf16": 0.01,
|
| 11 |
+
"min_tps_ratio_vs_reference": 0.9,
|
| 12 |
+
"max_weight_size_gb": 4.5
|
| 13 |
+
},
|
| 14 |
+
"experiments": [
|
| 15 |
+
{
|
| 16 |
+
"label": "bf16-head",
|
| 17 |
+
"precision": "bfloat16",
|
| 18 |
+
"passed": false,
|
| 19 |
+
"checks": {
|
| 20 |
+
"cer": true,
|
| 21 |
+
"digit_cer": true,
|
| 22 |
+
"table_score": true,
|
| 23 |
+
"throughput": false
|
| 24 |
+
},
|
| 25 |
+
"metrics": {
|
| 26 |
+
"mean_cer": 0.23190356458231498,
|
| 27 |
+
"mean_digit_cer": 0.08222376405546196,
|
| 28 |
+
"mean_table_score": 1.0,
|
| 29 |
+
"mean_tps": 271.0950821181683
|
| 30 |
+
},
|
| 31 |
+
"deltas": {
|
| 32 |
+
"cer_vs_bf16": -0.007284148710181432,
|
| 33 |
+
"digit_cer_vs_bf16": -0.005116203514816328,
|
| 34 |
+
"table_degradation_vs_bf16": 0.0,
|
| 35 |
+
"tps_ratio_vs_reference": 0.8668704848166195
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"label": "mxfp8-head",
|
| 40 |
+
"precision": "mxfp8",
|
| 41 |
+
"passed": false,
|
| 42 |
+
"checks": {
|
| 43 |
+
"cer": false,
|
| 44 |
+
"digit_cer": true,
|
| 45 |
+
"table_score": true,
|
| 46 |
+
"throughput": true
|
| 47 |
+
},
|
| 48 |
+
"metrics": {
|
| 49 |
+
"mean_cer": 0.26017003915600867,
|
| 50 |
+
"mean_digit_cer": 0.0769606061607251,
|
| 51 |
+
"mean_table_score": 1.0,
|
| 52 |
+
"mean_tps": 307.3938836867251
|
| 53 |
+
},
|
| 54 |
+
"deltas": {
|
| 55 |
+
"cer_vs_bf16": 0.020982325863512258,
|
| 56 |
+
"digit_cer_vs_bf16": -0.010379361409553187,
|
| 57 |
+
"table_degradation_vs_bf16": 0.0,
|
| 58 |
+
"tps_ratio_vs_reference": 0.9829417889071937
|
| 59 |
+
}
|
| 60 |
+
},
|
| 61 |
+
{
|
| 62 |
+
"label": "affine8-head",
|
| 63 |
+
"precision": "affine8",
|
| 64 |
+
"passed": true,
|
| 65 |
+
"checks": {
|
| 66 |
+
"cer": true,
|
| 67 |
+
"digit_cer": true,
|
| 68 |
+
"table_score": true,
|
| 69 |
+
"throughput": true
|
| 70 |
+
},
|
| 71 |
+
"metrics": {
|
| 72 |
+
"mean_cer": 0.23738438172232493,
|
| 73 |
+
"mean_digit_cer": 0.08573253598528652,
|
| 74 |
+
"mean_table_score": 1.0,
|
| 75 |
+
"mean_tps": 301.24796539578256
|
| 76 |
+
},
|
| 77 |
+
"deltas": {
|
| 78 |
+
"cer_vs_bf16": -0.0018033315701714847,
|
| 79 |
+
"digit_cer_vs_bf16": -0.0016074315849917697,
|
| 80 |
+
"table_degradation_vs_bf16": 0.0,
|
| 81 |
+
"tps_ratio_vs_reference": 0.9632892185732532
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
],
|
| 85 |
+
"selected": {
|
| 86 |
+
"label": "affine8-head",
|
| 87 |
+
"precision": "affine8"
|
| 88 |
+
},
|
| 89 |
+
"precision_overrides": {
|
| 90 |
+
"language_model.lm_head": "affine8"
|
| 91 |
+
}
|
| 92 |
+
}
|
release/candidate_accuracy.json
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_path": "AX-Unlimited-OCR-3B-MoE-MLX-MXFP8",
|
| 3 |
+
"prompt": "<image>document parsing.",
|
| 4 |
+
"max_tokens": 1024,
|
| 5 |
+
"profile": "accurate",
|
| 6 |
+
"generation_settings": {
|
| 7 |
+
"temperature": 0.0,
|
| 8 |
+
"top_p": 1.0,
|
| 9 |
+
"repetition_penalty": 1.0,
|
| 10 |
+
"no_repeat_ngram_size": 35,
|
| 11 |
+
"ngram_window": 128
|
| 12 |
+
},
|
| 13 |
+
"num_images": 12,
|
| 14 |
+
"num_samples": 12,
|
| 15 |
+
"total_time_seconds": 31.473419997957535,
|
| 16 |
+
"mean_cer": 0.23738438172232493,
|
| 17 |
+
"mean_wer": 0.5707107552516416,
|
| 18 |
+
"num_digit_samples": 12,
|
| 19 |
+
"num_cjk_samples": 3,
|
| 20 |
+
"mean_digit_cer": 0.08573253598528652,
|
| 21 |
+
"mean_cjk_cer": 0.3333333333333333,
|
| 22 |
+
"num_table_samples": 3,
|
| 23 |
+
"mean_table_score": 1.0,
|
| 24 |
+
"mean_repetition_rate": 0.0,
|
| 25 |
+
"per_file": [
|
| 26 |
+
{
|
| 27 |
+
"file": "0001.png",
|
| 28 |
+
"cer": 0.020480854853072127,
|
| 29 |
+
"wer": 0.18385650224215247,
|
| 30 |
+
"digit_cer": 0.0,
|
| 31 |
+
"cjk_cer": 0.0,
|
| 32 |
+
"repetition_rate": 0.0,
|
| 33 |
+
"table_score": null,
|
| 34 |
+
"elapsed_seconds": 3.815771791996667,
|
| 35 |
+
"ref_length": 1345,
|
| 36 |
+
"hyp_length": 1349,
|
| 37 |
+
"ref_digit_count": 86,
|
| 38 |
+
"ref_cjk_count": 0
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"file": "0002.png",
|
| 42 |
+
"cer": 0.9327354260089686,
|
| 43 |
+
"wer": 0.5089820359281437,
|
| 44 |
+
"digit_cer": 0.17894736842105263,
|
| 45 |
+
"cjk_cer": 0.0,
|
| 46 |
+
"repetition_rate": 0.0,
|
| 47 |
+
"table_score": null,
|
| 48 |
+
"elapsed_seconds": 4.953415083000436,
|
| 49 |
+
"ref_length": 835,
|
| 50 |
+
"hyp_length": 1310,
|
| 51 |
+
"ref_digit_count": 95,
|
| 52 |
+
"ref_cjk_count": 0
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"file": "0003.png",
|
| 56 |
+
"cer": 0.3512064343163539,
|
| 57 |
+
"wer": 0.3317757009345794,
|
| 58 |
+
"digit_cer": 0.14814814814814814,
|
| 59 |
+
"cjk_cer": 0.0,
|
| 60 |
+
"repetition_rate": 0.0,
|
| 61 |
+
"table_score": null,
|
| 62 |
+
"elapsed_seconds": 3.5391229999950156,
|
| 63 |
+
"ref_length": 1332,
|
| 64 |
+
"hyp_length": 1672,
|
| 65 |
+
"ref_digit_count": 27,
|
| 66 |
+
"ref_cjk_count": 0
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"file": "0004.png",
|
| 70 |
+
"cer": 0.38176895306859204,
|
| 71 |
+
"wer": 0.36936936936936937,
|
| 72 |
+
"digit_cer": 0.1016949152542373,
|
| 73 |
+
"cjk_cer": 0.0,
|
| 74 |
+
"repetition_rate": 0.0,
|
| 75 |
+
"table_score": null,
|
| 76 |
+
"elapsed_seconds": 3.735332540993113,
|
| 77 |
+
"ref_length": 1329,
|
| 78 |
+
"hyp_length": 1705,
|
| 79 |
+
"ref_digit_count": 59,
|
| 80 |
+
"ref_cjk_count": 0
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"file": "0005.png",
|
| 84 |
+
"cer": 0.17715617715617715,
|
| 85 |
+
"wer": 1.5,
|
| 86 |
+
"digit_cer": 0.2,
|
| 87 |
+
"cjk_cer": 0.0,
|
| 88 |
+
"repetition_rate": 0.0,
|
| 89 |
+
"table_score": 1.0,
|
| 90 |
+
"elapsed_seconds": 2.769828624994261,
|
| 91 |
+
"ref_length": 436,
|
| 92 |
+
"hyp_length": 508,
|
| 93 |
+
"ref_digit_count": 70,
|
| 94 |
+
"ref_cjk_count": 0
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
"file": "0006.png",
|
| 98 |
+
"cer": 0.17715617715617715,
|
| 99 |
+
"wer": 1.5,
|
| 100 |
+
"digit_cer": 0.2,
|
| 101 |
+
"cjk_cer": 0.0,
|
| 102 |
+
"repetition_rate": 0.0,
|
| 103 |
+
"table_score": 1.0,
|
| 104 |
+
"elapsed_seconds": 2.7706372079992434,
|
| 105 |
+
"ref_length": 436,
|
| 106 |
+
"hyp_length": 508,
|
| 107 |
+
"ref_digit_count": 70,
|
| 108 |
+
"ref_cjk_count": 0
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"file": "0007.png",
|
| 112 |
+
"cer": 0.17715617715617715,
|
| 113 |
+
"wer": 1.5,
|
| 114 |
+
"digit_cer": 0.2,
|
| 115 |
+
"cjk_cer": 0.0,
|
| 116 |
+
"repetition_rate": 0.0,
|
| 117 |
+
"table_score": 1.0,
|
| 118 |
+
"elapsed_seconds": 2.726061583001865,
|
| 119 |
+
"ref_length": 436,
|
| 120 |
+
"hyp_length": 508,
|
| 121 |
+
"ref_digit_count": 70,
|
| 122 |
+
"ref_cjk_count": 0
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"file": "0008.png",
|
| 126 |
+
"cer": 0.0,
|
| 127 |
+
"wer": 0.0,
|
| 128 |
+
"digit_cer": 0.0,
|
| 129 |
+
"cjk_cer": 0.0,
|
| 130 |
+
"repetition_rate": 0.0,
|
| 131 |
+
"table_score": null,
|
| 132 |
+
"elapsed_seconds": 1.4497268339910079,
|
| 133 |
+
"ref_length": 92,
|
| 134 |
+
"hyp_length": 92,
|
| 135 |
+
"ref_digit_count": 26,
|
| 136 |
+
"ref_cjk_count": 41
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"file": "0009.png",
|
| 140 |
+
"cer": 0.0,
|
| 141 |
+
"wer": 0.0,
|
| 142 |
+
"digit_cer": 0.0,
|
| 143 |
+
"cjk_cer": 0.0,
|
| 144 |
+
"repetition_rate": 0.0,
|
| 145 |
+
"table_score": null,
|
| 146 |
+
"elapsed_seconds": 1.4638195829902543,
|
| 147 |
+
"ref_length": 93,
|
| 148 |
+
"hyp_length": 93,
|
| 149 |
+
"ref_digit_count": 25,
|
| 150 |
+
"ref_cjk_count": 42
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"file": "0010.png",
|
| 154 |
+
"cer": 0.6309523809523809,
|
| 155 |
+
"wer": 0.9545454545454546,
|
| 156 |
+
"digit_cer": 0.0,
|
| 157 |
+
"cjk_cer": 1.0,
|
| 158 |
+
"repetition_rate": 0.0,
|
| 159 |
+
"table_score": null,
|
| 160 |
+
"elapsed_seconds": 1.4698359579924727,
|
| 161 |
+
"ref_length": 105,
|
| 162 |
+
"hyp_length": 110,
|
| 163 |
+
"ref_digit_count": 25,
|
| 164 |
+
"ref_cjk_count": 44
|
| 165 |
+
},
|
| 166 |
+
{
|
| 167 |
+
"file": "0011.png",
|
| 168 |
+
"cer": 0.0,
|
| 169 |
+
"wer": 0.0,
|
| 170 |
+
"digit_cer": 0.0,
|
| 171 |
+
"cjk_cer": 0.0,
|
| 172 |
+
"repetition_rate": 0.0,
|
| 173 |
+
"table_score": null,
|
| 174 |
+
"elapsed_seconds": 1.3830854999978328,
|
| 175 |
+
"ref_length": 118,
|
| 176 |
+
"hyp_length": 118,
|
| 177 |
+
"ref_digit_count": 27,
|
| 178 |
+
"ref_cjk_count": 0
|
| 179 |
+
},
|
| 180 |
+
{
|
| 181 |
+
"file": "0012.png",
|
| 182 |
+
"cer": 0.0,
|
| 183 |
+
"wer": 0.0,
|
| 184 |
+
"digit_cer": 0.0,
|
| 185 |
+
"cjk_cer": 0.0,
|
| 186 |
+
"repetition_rate": 0.0,
|
| 187 |
+
"table_score": null,
|
| 188 |
+
"elapsed_seconds": 1.3967822910053656,
|
| 189 |
+
"ref_length": 118,
|
| 190 |
+
"hyp_length": 118,
|
| 191 |
+
"ref_digit_count": 27,
|
| 192 |
+
"ref_cjk_count": 0
|
| 193 |
+
}
|
| 194 |
+
]
|
| 195 |
+
}
|
release/candidate_performance.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_path": "AX-Unlimited-OCR-3B-MoE-MLX-MXFP8",
|
| 3 |
+
"image_path": "test_invoice.png",
|
| 4 |
+
"prompt": "<image>document parsing.",
|
| 5 |
+
"max_tokens": 256,
|
| 6 |
+
"system": {
|
| 7 |
+
"platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O",
|
| 8 |
+
"processor": "arm",
|
| 9 |
+
"python_version": "3.14.6",
|
| 10 |
+
"machine": "arm64",
|
| 11 |
+
"mlx_version": "0.32.0",
|
| 12 |
+
"mlx_vlm_version": "0.6.6",
|
| 13 |
+
"chip": "Apple M3 Max",
|
| 14 |
+
"total_memory_gb": 128.0
|
| 15 |
+
},
|
| 16 |
+
"model_load_time_seconds": 0.8421083340072073,
|
| 17 |
+
"memory_after_load_mb": 4008.296875,
|
| 18 |
+
"num_warmup": 1,
|
| 19 |
+
"num_runs": 3,
|
| 20 |
+
"mean_tps": 297.2042493840031,
|
| 21 |
+
"std_tps": 3.7161843770763436,
|
| 22 |
+
"mean_elapsed_seconds": 1.2716583613364492,
|
| 23 |
+
"mean_peak_memory_mb": 5181.5234390000005,
|
| 24 |
+
"runs": [
|
| 25 |
+
{
|
| 26 |
+
"run": 1,
|
| 27 |
+
"elapsed_seconds": 1.283014083004673,
|
| 28 |
+
"tokens_generated": 256,
|
| 29 |
+
"tokens_generated_source": "mlx-vlm token count",
|
| 30 |
+
"tokens_per_second": 293.6595631095472,
|
| 31 |
+
"tokens_per_second_source": "mlx-vlm generation_tps",
|
| 32 |
+
"peak_memory_mb": 5181.347799,
|
| 33 |
+
"memory_delta_mb": 1337.1867550000002,
|
| 34 |
+
"process_peak_rss_mb": 4021.109375,
|
| 35 |
+
"prompt_tokens": 697,
|
| 36 |
+
"prompt_tokens_per_second": 1793.8031467011067,
|
| 37 |
+
"finish_reason": "length"
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"run": 2,
|
| 41 |
+
"elapsed_seconds": 1.276783292007167,
|
| 42 |
+
"tokens_generated": 256,
|
| 43 |
+
"tokens_generated_source": "mlx-vlm token count",
|
| 44 |
+
"tokens_per_second": 295.6163188867776,
|
| 45 |
+
"tokens_per_second_source": "mlx-vlm generation_tps",
|
| 46 |
+
"peak_memory_mb": 5181.611259,
|
| 47 |
+
"memory_delta_mb": 1337.188071,
|
| 48 |
+
"process_peak_rss_mb": 4021.640625,
|
| 49 |
+
"prompt_tokens": 697,
|
| 50 |
+
"prompt_tokens_per_second": 1787.3186918039762,
|
| 51 |
+
"finish_reason": "length"
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"run": 3,
|
| 55 |
+
"elapsed_seconds": 1.2551777089975076,
|
| 56 |
+
"tokens_generated": 256,
|
| 57 |
+
"tokens_generated_source": "mlx-vlm token count",
|
| 58 |
+
"tokens_per_second": 302.3368661556844,
|
| 59 |
+
"tokens_per_second_source": "mlx-vlm generation_tps",
|
| 60 |
+
"peak_memory_mb": 5181.611259,
|
| 61 |
+
"memory_delta_mb": 1337.188071,
|
| 62 |
+
"process_peak_rss_mb": 4021.90625,
|
| 63 |
+
"prompt_tokens": 697,
|
| 64 |
+
"prompt_tokens_per_second": 1798.8601642526962,
|
| 65 |
+
"finish_reason": "length"
|
| 66 |
+
}
|
| 67 |
+
]
|
| 68 |
+
}
|
release/candidate_rswa.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_path": "AX-Unlimited-OCR-3B-MoE-MLX-MXFP8",
|
| 3 |
+
"image_path": "test_invoice.png",
|
| 4 |
+
"prompt": "<image>document parsing.",
|
| 5 |
+
"force_min_tokens": 8192,
|
| 6 |
+
"forced_eos_token_ids": [
|
| 7 |
+
1
|
| 8 |
+
],
|
| 9 |
+
"platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O",
|
| 10 |
+
"processor": "arm",
|
| 11 |
+
"baseline_memory_mb": 3844.161032,
|
| 12 |
+
"test_results": [
|
| 13 |
+
{
|
| 14 |
+
"max_tokens": 512,
|
| 15 |
+
"tokens_generated": 512,
|
| 16 |
+
"tokens_generated_source": "mlx-vlm token count",
|
| 17 |
+
"tokens_generated_reliable": true,
|
| 18 |
+
"elapsed_seconds": 2.4322984160098713,
|
| 19 |
+
"mean_tps": 264.34124967086365,
|
| 20 |
+
"tps_source": "mlx-vlm generation_tps",
|
| 21 |
+
"peak_memory_mb": 5181.350593,
|
| 22 |
+
"memory_growth_mb": 1337.1895610000001,
|
| 23 |
+
"repetition_rate": 0.0,
|
| 24 |
+
"completed_naturally": false,
|
| 25 |
+
"finish_reason": "length",
|
| 26 |
+
"forced_minimum_tokens": 512,
|
| 27 |
+
"status": "success"
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"max_tokens": 2048,
|
| 31 |
+
"tokens_generated": 2048,
|
| 32 |
+
"tokens_generated_source": "mlx-vlm token count",
|
| 33 |
+
"tokens_generated_reliable": true,
|
| 34 |
+
"elapsed_seconds": 8.234726583992597,
|
| 35 |
+
"mean_tps": 261.8525825264912,
|
| 36 |
+
"tps_source": "mlx-vlm generation_tps",
|
| 37 |
+
"peak_memory_mb": 5181.612737,
|
| 38 |
+
"memory_growth_mb": 1337.1895490000002,
|
| 39 |
+
"repetition_rate": 0.09565217391304348,
|
| 40 |
+
"completed_naturally": false,
|
| 41 |
+
"finish_reason": "length",
|
| 42 |
+
"forced_minimum_tokens": 2048,
|
| 43 |
+
"status": "success"
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"max_tokens": 8192,
|
| 47 |
+
"tokens_generated": 8192,
|
| 48 |
+
"tokens_generated_source": "mlx-vlm token count",
|
| 49 |
+
"tokens_generated_reliable": true,
|
| 50 |
+
"elapsed_seconds": 31.729143417003797,
|
| 51 |
+
"mean_tps": 261.56365755570715,
|
| 52 |
+
"tps_source": "mlx-vlm generation_tps",
|
| 53 |
+
"peak_memory_mb": 5181.612737,
|
| 54 |
+
"memory_growth_mb": 1337.1895490000002,
|
| 55 |
+
"repetition_rate": 0.09565217391304348,
|
| 56 |
+
"completed_naturally": false,
|
| 57 |
+
"finish_reason": "length",
|
| 58 |
+
"forced_minimum_tokens": 8192,
|
| 59 |
+
"status": "success"
|
| 60 |
+
}
|
| 61 |
+
],
|
| 62 |
+
"analysis": {
|
| 63 |
+
"cache_appears_bounded": true,
|
| 64 |
+
"cache_analysis_reason": "Peak memory stayed within 64 MB across a >=4x token span",
|
| 65 |
+
"tps_stable_over_length": true,
|
| 66 |
+
"max_successful_tokens": 8192,
|
| 67 |
+
"max_repetition_rate": 0.09565217391304348
|
| 68 |
+
},
|
| 69 |
+
"pass_conditions": {
|
| 70 |
+
"cache_bounded": true,
|
| 71 |
+
"tps_stable": true,
|
| 72 |
+
"8k_test_passed": true
|
| 73 |
+
},
|
| 74 |
+
"passed": true
|
| 75 |
+
}
|
release/generated_precision_map.json
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"vision_model": "bfloat16",
|
| 3 |
+
"projector": "mxfp8",
|
| 4 |
+
"language_model.model.embed_tokens": "mxfp8",
|
| 5 |
+
"language_model.model.norm": "bfloat16",
|
| 6 |
+
"language_model.lm_head": "affine8",
|
| 7 |
+
"language_model.model.layers.*.input_layernorm": "bfloat16",
|
| 8 |
+
"language_model.model.layers.*.post_attention_layernorm": "bfloat16",
|
| 9 |
+
"language_model.model.layers.*.self_attn.q_proj": "mxfp8",
|
| 10 |
+
"language_model.model.layers.*.self_attn.k_proj": "mxfp8",
|
| 11 |
+
"language_model.model.layers.*.self_attn.v_proj": "mxfp8",
|
| 12 |
+
"language_model.model.layers.*.self_attn.o_proj": "mxfp8",
|
| 13 |
+
"language_model.model.layers.*.mlp.gate_proj": "mxfp8",
|
| 14 |
+
"language_model.model.layers.*.mlp.up_proj": "mxfp8",
|
| 15 |
+
"language_model.model.layers.*.mlp.down_proj": "mxfp8",
|
| 16 |
+
"language_model.model.layers.*.mlp.shared_experts.gate_proj": "mxfp8",
|
| 17 |
+
"language_model.model.layers.*.mlp.shared_experts.up_proj": "mxfp8",
|
| 18 |
+
"language_model.model.layers.*.mlp.shared_experts.down_proj": "mxfp8",
|
| 19 |
+
"language_model.model.layers.*.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 20 |
+
"language_model.model.layers.*.mlp.switch_mlp.up_proj": "mxfp8",
|
| 21 |
+
"language_model.model.layers.*.mlp.switch_mlp.down_proj": "mxfp8",
|
| 22 |
+
"language_model.model.layers.*.mlp.gate": "bfloat16",
|
| 23 |
+
"sam_model": "bfloat16",
|
| 24 |
+
"_generated_from": {
|
| 25 |
+
"source_model": "baidu/Unlimited-OCR",
|
| 26 |
+
"thresholds": {
|
| 27 |
+
"cer_delta": 0.02,
|
| 28 |
+
"digit_cer_delta": 0.02,
|
| 29 |
+
"table_score_degradation": 0.01
|
| 30 |
+
},
|
| 31 |
+
"decisions": {
|
| 32 |
+
"vision_encoder": {
|
| 33 |
+
"precision": "bfloat16",
|
| 34 |
+
"reasons": [
|
| 35 |
+
"cer_delta=2.423686",
|
| 36 |
+
"digit_cer_delta=1.358023",
|
| 37 |
+
"table_score_degradation=1.000000",
|
| 38 |
+
"protected OCR-critical group"
|
| 39 |
+
]
|
| 40 |
+
},
|
| 41 |
+
"vision_projector": {
|
| 42 |
+
"precision": "mxfp8",
|
| 43 |
+
"reasons": [
|
| 44 |
+
"all measured quality deltas were non-degrading"
|
| 45 |
+
]
|
| 46 |
+
},
|
| 47 |
+
"token_embeddings": {
|
| 48 |
+
"precision": "mxfp8",
|
| 49 |
+
"reasons": [
|
| 50 |
+
"all measured quality deltas were non-degrading"
|
| 51 |
+
]
|
| 52 |
+
},
|
| 53 |
+
"attention_q_proj": {
|
| 54 |
+
"precision": "base-map",
|
| 55 |
+
"reasons": [
|
| 56 |
+
"within thresholds"
|
| 57 |
+
]
|
| 58 |
+
},
|
| 59 |
+
"attention_k_proj": {
|
| 60 |
+
"precision": "base-map",
|
| 61 |
+
"reasons": [
|
| 62 |
+
"within thresholds"
|
| 63 |
+
]
|
| 64 |
+
},
|
| 65 |
+
"attention_v_proj": {
|
| 66 |
+
"precision": "base-map",
|
| 67 |
+
"reasons": [
|
| 68 |
+
"within thresholds"
|
| 69 |
+
]
|
| 70 |
+
},
|
| 71 |
+
"attention_o_proj": {
|
| 72 |
+
"precision": "base-map",
|
| 73 |
+
"reasons": [
|
| 74 |
+
"within thresholds"
|
| 75 |
+
]
|
| 76 |
+
},
|
| 77 |
+
"dense_mlp": {
|
| 78 |
+
"precision": "base-map",
|
| 79 |
+
"reasons": [
|
| 80 |
+
"within thresholds"
|
| 81 |
+
]
|
| 82 |
+
},
|
| 83 |
+
"shared_experts": {
|
| 84 |
+
"precision": "base-map",
|
| 85 |
+
"reasons": [
|
| 86 |
+
"within thresholds"
|
| 87 |
+
]
|
| 88 |
+
},
|
| 89 |
+
"routed_experts": {
|
| 90 |
+
"precision": "base-map",
|
| 91 |
+
"reasons": [
|
| 92 |
+
"within thresholds"
|
| 93 |
+
]
|
| 94 |
+
},
|
| 95 |
+
"lm_head": {
|
| 96 |
+
"precision": "affine8",
|
| 97 |
+
"reasons": [
|
| 98 |
+
"selected by joint quality/throughput calibration: affine8-head"
|
| 99 |
+
]
|
| 100 |
+
}
|
| 101 |
+
},
|
| 102 |
+
"calibration": {
|
| 103 |
+
"selected": {
|
| 104 |
+
"label": "affine8-head",
|
| 105 |
+
"precision": "affine8"
|
| 106 |
+
},
|
| 107 |
+
"selection_policy": "fastest candidate passing existing quality and throughput limits"
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
}
|
release/provenance.json
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"created_at": "2026-07-20T23:05:17.950531+00:00",
|
| 3 |
+
"python": "3.14.6",
|
| 4 |
+
"platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O",
|
| 5 |
+
"machine": "arm64",
|
| 6 |
+
"processor": "arm",
|
| 7 |
+
"versions": {
|
| 8 |
+
"mlx": "0.32.0",
|
| 9 |
+
"mlx-vlm": "0.6.6",
|
| 10 |
+
"huggingface-hub": "1.24.0",
|
| 11 |
+
"numpy": "2.5.1",
|
| 12 |
+
"Pillow": "12.3.0"
|
| 13 |
+
},
|
| 14 |
+
"source_model": "baidu/Unlimited-OCR",
|
| 15 |
+
"source_local_name": "Unlimited-OCR",
|
| 16 |
+
"source_config": {
|
| 17 |
+
"path": "config.json",
|
| 18 |
+
"sha256": "27246d03fd670904ec9601b1cb0861fbb79ec076830771daa8d943d6229946f9"
|
| 19 |
+
},
|
| 20 |
+
"reference_model": "sahilchachra/unlimited-ocr-mxfp8-mlx",
|
| 21 |
+
"target_repo": "AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8",
|
| 22 |
+
"dataset": {
|
| 23 |
+
"name": "eval_dataset",
|
| 24 |
+
"num_samples": 12,
|
| 25 |
+
"num_digit_samples": 12,
|
| 26 |
+
"num_cjk_samples": 3,
|
| 27 |
+
"num_table_samples": 3,
|
| 28 |
+
"category_counts": {
|
| 29 |
+
"cjk_document": 3,
|
| 30 |
+
"english_document": 4,
|
| 31 |
+
"low_quality_scan": 2,
|
| 32 |
+
"table_numeric": 3
|
| 33 |
+
},
|
| 34 |
+
"manifest_sha256": "94d96ec344b02b8c4b413eafe524d6844bddcb00705af4b3fccea4c297c22509",
|
| 35 |
+
"content_sha256": "78ca3e7c9c7584aea30b04176716a6dadf49ccf0f2bead959cbaeb9aeac68f30",
|
| 36 |
+
"files": [
|
| 37 |
+
{
|
| 38 |
+
"path": "images/0001.png",
|
| 39 |
+
"size": 243645,
|
| 40 |
+
"sha256": "8fad74c7e8f314075f34d5b3b3e6272a2c22a90e960dcf8074039b1f065895b4"
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"path": "ground_truth/0001.txt",
|
| 44 |
+
"size": 1346,
|
| 45 |
+
"sha256": "a6f35a418836e8ba0667dc42d663219b07b65cbbf7a4627b2319f0fa44d11f8e"
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"path": "images/0002.png",
|
| 49 |
+
"size": 208699,
|
| 50 |
+
"sha256": "31f2d9c651112e963366e5e39f3da4ff8198ec41e2187c3c8fb5fec0edd2f64c"
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"path": "ground_truth/0002.txt",
|
| 54 |
+
"size": 838,
|
| 55 |
+
"sha256": "a05196b747f6898102fc56e23cae31c32754b985bc3eab0abe60d955bfadff68"
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"path": "images/0003.png",
|
| 59 |
+
"size": 181040,
|
| 60 |
+
"sha256": "40330138b25a7ce9bf142a672ad0a6253517bf076af66591c4431490eff1ecfc"
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"path": "ground_truth/0003.txt",
|
| 64 |
+
"size": 1333,
|
| 65 |
+
"sha256": "517ab5af1c090227d4dc2473ac422ecd9301e2e21ba0cc1c54d08476b1903d7e"
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"path": "images/0004.png",
|
| 69 |
+
"size": 220208,
|
| 70 |
+
"sha256": "4316ba5ebe86c62331361e6d23329adede5e9eecf50a70db16b5439d95f5e23e"
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"path": "ground_truth/0004.txt",
|
| 74 |
+
"size": 1330,
|
| 75 |
+
"sha256": "df3b4afaee0f3ffe0984c2f1d1201c1762bc70f4ffbc1bfe9b1242e17d794148"
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"path": "images/0005.png",
|
| 79 |
+
"size": 38984,
|
| 80 |
+
"sha256": "1532b86fe28d7331379c73b62cfb0b0ae1448824566684021df262e4741b06a4"
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"path": "ground_truth/0005.txt",
|
| 84 |
+
"size": 449,
|
| 85 |
+
"sha256": "fb8ebea089dacff057d98feb6450436f3f6db65f0bac29513d3417e94451686e"
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"path": "images/0006.png",
|
| 89 |
+
"size": 38512,
|
| 90 |
+
"sha256": "8c1bf51f86f0f22c8898cec8c77049b6f023b77aab77f55be40f75b477ab6383"
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"path": "ground_truth/0006.txt",
|
| 94 |
+
"size": 449,
|
| 95 |
+
"sha256": "f60ff098ac431b1021fb85f810dd6264c1c021fab0810ab97ce304c5eb802598"
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"path": "images/0007.png",
|
| 99 |
+
"size": 38911,
|
| 100 |
+
"sha256": "a87e7f53196e9d3f72af611394297ec64e9eb222d2872b62f0c5a2014d83ecfc"
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"path": "ground_truth/0007.txt",
|
| 104 |
+
"size": 449,
|
| 105 |
+
"sha256": "0381ec16f8895290dfc2b81f97933f5568a68bfe731cc4791272ff8269e57c0e"
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"path": "images/0008.png",
|
| 109 |
+
"size": 45665,
|
| 110 |
+
"sha256": "d9ff0cc9ffa639e485b0d445e1f4856f479ed9e2e485b1ca1ece9a9a043bc6c8"
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"path": "ground_truth/0008.txt",
|
| 114 |
+
"size": 185,
|
| 115 |
+
"sha256": "2dbeb40c0172dc618c7298a87e131153f51682dcaa33efefa67b7e50ecd7a8d1"
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"path": "images/0009.png",
|
| 119 |
+
"size": 40784,
|
| 120 |
+
"sha256": "f93ea216d77bfdb23f6c8c113e33c946318d6eac6ef3bdaf2e2f2c0d86c154c9"
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"path": "ground_truth/0009.txt",
|
| 124 |
+
"size": 188,
|
| 125 |
+
"sha256": "957232168770c2a15efeb33126a7d2d8141ce34782106b979e4b31acfa4f9bee"
|
| 126 |
+
},
|
| 127 |
+
{
|
| 128 |
+
"path": "images/0010.png",
|
| 129 |
+
"size": 20512,
|
| 130 |
+
"sha256": "5ff52fd340da20784c4c934cbac3ae9a0788b7d1654d71934100624842ddf764"
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"path": "ground_truth/0010.txt",
|
| 134 |
+
"size": 194,
|
| 135 |
+
"sha256": "2f9054687e5eb369e35d985bc2fe9d2d37ffeb8cafe204c3e81830f4431f5793"
|
| 136 |
+
},
|
| 137 |
+
{
|
| 138 |
+
"path": "images/0011.png",
|
| 139 |
+
"size": 486656,
|
| 140 |
+
"sha256": "74e878d54295c224481f0f1b51a670459b855b16492c246c27636d71f4666e68"
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"path": "ground_truth/0011.txt",
|
| 144 |
+
"size": 119,
|
| 145 |
+
"sha256": "8f8bc16447a12c17e4036c4c5fd936faef227310a564de983ebe2f4f22742d83"
|
| 146 |
+
},
|
| 147 |
+
{
|
| 148 |
+
"path": "images/0012.png",
|
| 149 |
+
"size": 515472,
|
| 150 |
+
"sha256": "9a6e49af19e893000b8e96bece22fe99fedd3741e7ac3dede04a112ea77b5b5c"
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"path": "ground_truth/0012.txt",
|
| 154 |
+
"size": 119,
|
| 155 |
+
"sha256": "1ccf00fe53da00ba3a2340dbe95e1692e87efccdc04b191c0f1d52dab23169a7"
|
| 156 |
+
}
|
| 157 |
+
]
|
| 158 |
+
},
|
| 159 |
+
"smoke_image": {
|
| 160 |
+
"name": "test_invoice.png",
|
| 161 |
+
"sha256": "6f2c3a686d13604b6721b1e9596408ec3b3eb38227b6d5f2d930a0ce05c8cb14"
|
| 162 |
+
},
|
| 163 |
+
"parameters": {
|
| 164 |
+
"accuracy_tokens": 1024,
|
| 165 |
+
"performance_tokens": 256,
|
| 166 |
+
"performance_warmup": 1,
|
| 167 |
+
"performance_runs": 3,
|
| 168 |
+
"rswa_lengths": [
|
| 169 |
+
512,
|
| 170 |
+
2048,
|
| 171 |
+
8192
|
| 172 |
+
]
|
| 173 |
+
}
|
| 174 |
+
}
|
release/reference_accuracy.json
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_path": "sahilchachra/unlimited-ocr-mxfp8-mlx",
|
| 3 |
+
"prompt": "<image>document parsing.",
|
| 4 |
+
"max_tokens": 1024,
|
| 5 |
+
"profile": "accurate",
|
| 6 |
+
"generation_settings": {
|
| 7 |
+
"temperature": 0.0,
|
| 8 |
+
"top_p": 1.0,
|
| 9 |
+
"repetition_penalty": 1.0,
|
| 10 |
+
"no_repeat_ngram_size": 35,
|
| 11 |
+
"ngram_window": 128
|
| 12 |
+
},
|
| 13 |
+
"num_images": 12,
|
| 14 |
+
"num_samples": 12,
|
| 15 |
+
"total_time_seconds": 26.09562641500088,
|
| 16 |
+
"mean_cer": 1.057248773567081,
|
| 17 |
+
"mean_wer": 0.9346679608165291,
|
| 18 |
+
"num_digit_samples": 12,
|
| 19 |
+
"num_cjk_samples": 3,
|
| 20 |
+
"mean_digit_cer": 0.20915311607827292,
|
| 21 |
+
"mean_cjk_cer": 1.0,
|
| 22 |
+
"num_table_samples": 3,
|
| 23 |
+
"mean_table_score": 1.0,
|
| 24 |
+
"mean_repetition_rate": 0.00026371308016877635,
|
| 25 |
+
"per_file": [
|
| 26 |
+
{
|
| 27 |
+
"file": "0001.png",
|
| 28 |
+
"cer": 0.06144256455921639,
|
| 29 |
+
"wer": 0.20179372197309417,
|
| 30 |
+
"digit_cer": 0.3023255813953488,
|
| 31 |
+
"cjk_cer": 0.0,
|
| 32 |
+
"repetition_rate": 0.0,
|
| 33 |
+
"table_score": null,
|
| 34 |
+
"elapsed_seconds": 3.459972665994428,
|
| 35 |
+
"ref_length": 1345,
|
| 36 |
+
"hyp_length": 1382,
|
| 37 |
+
"ref_digit_count": 86,
|
| 38 |
+
"ref_cjk_count": 0
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"file": "0002.png",
|
| 42 |
+
"cer": 0.47234678624813153,
|
| 43 |
+
"wer": 0.5988023952095808,
|
| 44 |
+
"digit_cer": 0.5578947368421052,
|
| 45 |
+
"cjk_cer": 0.0,
|
| 46 |
+
"repetition_rate": 0.0,
|
| 47 |
+
"table_score": null,
|
| 48 |
+
"elapsed_seconds": 4.406020459005958,
|
| 49 |
+
"ref_length": 835,
|
| 50 |
+
"hyp_length": 771,
|
| 51 |
+
"ref_digit_count": 95,
|
| 52 |
+
"ref_cjk_count": 0
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"file": "0003.png",
|
| 56 |
+
"cer": 1.1751563896336015,
|
| 57 |
+
"wer": 1.439252336448598,
|
| 58 |
+
"digit_cer": 0.48148148148148145,
|
| 59 |
+
"cjk_cer": 0.0,
|
| 60 |
+
"repetition_rate": 0.0031645569620253164,
|
| 61 |
+
"table_score": null,
|
| 62 |
+
"elapsed_seconds": 4.409982333003427,
|
| 63 |
+
"ref_length": 1332,
|
| 64 |
+
"hyp_length": 2457,
|
| 65 |
+
"ref_digit_count": 27,
|
| 66 |
+
"ref_cjk_count": 0
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"file": "0004.png",
|
| 70 |
+
"cer": 0.7319494584837545,
|
| 71 |
+
"wer": 0.6216216216216216,
|
| 72 |
+
"digit_cer": 0.288135593220339,
|
| 73 |
+
"cjk_cer": 0.0,
|
| 74 |
+
"repetition_rate": 0.0,
|
| 75 |
+
"table_score": null,
|
| 76 |
+
"elapsed_seconds": 4.450652749990695,
|
| 77 |
+
"ref_length": 1329,
|
| 78 |
+
"hyp_length": 1794,
|
| 79 |
+
"ref_digit_count": 59,
|
| 80 |
+
"ref_cjk_count": 0
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"file": "0005.png",
|
| 84 |
+
"cer": 0.17715617715617715,
|
| 85 |
+
"wer": 1.5,
|
| 86 |
+
"digit_cer": 0.2,
|
| 87 |
+
"cjk_cer": 0.0,
|
| 88 |
+
"repetition_rate": 0.0,
|
| 89 |
+
"table_score": 1.0,
|
| 90 |
+
"elapsed_seconds": 1.2356766660086578,
|
| 91 |
+
"ref_length": 436,
|
| 92 |
+
"hyp_length": 508,
|
| 93 |
+
"ref_digit_count": 70,
|
| 94 |
+
"ref_cjk_count": 0
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
"file": "0006.png",
|
| 98 |
+
"cer": 0.17715617715617715,
|
| 99 |
+
"wer": 1.5,
|
| 100 |
+
"digit_cer": 0.2,
|
| 101 |
+
"cjk_cer": 0.0,
|
| 102 |
+
"repetition_rate": 0.0,
|
| 103 |
+
"table_score": 1.0,
|
| 104 |
+
"elapsed_seconds": 1.2516661249974277,
|
| 105 |
+
"ref_length": 436,
|
| 106 |
+
"hyp_length": 508,
|
| 107 |
+
"ref_digit_count": 70,
|
| 108 |
+
"ref_cjk_count": 0
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"file": "0007.png",
|
| 112 |
+
"cer": 0.17715617715617715,
|
| 113 |
+
"wer": 1.5,
|
| 114 |
+
"digit_cer": 0.2,
|
| 115 |
+
"cjk_cer": 0.0,
|
| 116 |
+
"repetition_rate": 0.0,
|
| 117 |
+
"table_score": 1.0,
|
| 118 |
+
"elapsed_seconds": 1.274972874991363,
|
| 119 |
+
"ref_length": 436,
|
| 120 |
+
"hyp_length": 508,
|
| 121 |
+
"ref_digit_count": 70,
|
| 122 |
+
"ref_cjk_count": 0
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"file": "0008.png",
|
| 126 |
+
"cer": 1.6867469879518073,
|
| 127 |
+
"wer": 0.9,
|
| 128 |
+
"digit_cer": 0.0,
|
| 129 |
+
"cjk_cer": 1.0,
|
| 130 |
+
"repetition_rate": 0.0,
|
| 131 |
+
"table_score": null,
|
| 132 |
+
"elapsed_seconds": 1.1017809159966419,
|
| 133 |
+
"ref_length": 92,
|
| 134 |
+
"hyp_length": 187,
|
| 135 |
+
"ref_digit_count": 26,
|
| 136 |
+
"ref_cjk_count": 41
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"file": "0009.png",
|
| 140 |
+
"cer": 5.670731707317073,
|
| 141 |
+
"wer": 1.0,
|
| 142 |
+
"digit_cer": 0.0,
|
| 143 |
+
"cjk_cer": 1.0,
|
| 144 |
+
"repetition_rate": 0.0,
|
| 145 |
+
"table_score": null,
|
| 146 |
+
"elapsed_seconds": 1.35718295800325,
|
| 147 |
+
"ref_length": 93,
|
| 148 |
+
"hyp_length": 509,
|
| 149 |
+
"ref_digit_count": 25,
|
| 150 |
+
"ref_cjk_count": 42
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"file": "0010.png",
|
| 154 |
+
"cer": 2.357142857142857,
|
| 155 |
+
"wer": 1.9545454545454546,
|
| 156 |
+
"digit_cer": 0.28,
|
| 157 |
+
"cjk_cer": 1.0,
|
| 158 |
+
"repetition_rate": 0.0,
|
| 159 |
+
"table_score": null,
|
| 160 |
+
"elapsed_seconds": 1.1220285840099677,
|
| 161 |
+
"ref_length": 105,
|
| 162 |
+
"hyp_length": 269,
|
| 163 |
+
"ref_digit_count": 25,
|
| 164 |
+
"ref_cjk_count": 44
|
| 165 |
+
},
|
| 166 |
+
{
|
| 167 |
+
"file": "0011.png",
|
| 168 |
+
"cer": 0.0,
|
| 169 |
+
"wer": 0.0,
|
| 170 |
+
"digit_cer": 0.0,
|
| 171 |
+
"cjk_cer": 0.0,
|
| 172 |
+
"repetition_rate": 0.0,
|
| 173 |
+
"table_score": null,
|
| 174 |
+
"elapsed_seconds": 1.0181266669969773,
|
| 175 |
+
"ref_length": 118,
|
| 176 |
+
"hyp_length": 118,
|
| 177 |
+
"ref_digit_count": 27,
|
| 178 |
+
"ref_cjk_count": 0
|
| 179 |
+
},
|
| 180 |
+
{
|
| 181 |
+
"file": "0012.png",
|
| 182 |
+
"cer": 0.0,
|
| 183 |
+
"wer": 0.0,
|
| 184 |
+
"digit_cer": 0.0,
|
| 185 |
+
"cjk_cer": 0.0,
|
| 186 |
+
"repetition_rate": 0.0,
|
| 187 |
+
"table_score": null,
|
| 188 |
+
"elapsed_seconds": 1.0075634160020854,
|
| 189 |
+
"ref_length": 118,
|
| 190 |
+
"hyp_length": 118,
|
| 191 |
+
"ref_digit_count": 27,
|
| 192 |
+
"ref_cjk_count": 0
|
| 193 |
+
}
|
| 194 |
+
]
|
| 195 |
+
}
|
release/reference_performance.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_path": "sahilchachra/unlimited-ocr-mxfp8-mlx",
|
| 3 |
+
"image_path": "test_invoice.png",
|
| 4 |
+
"prompt": "<image>document parsing.",
|
| 5 |
+
"max_tokens": 256,
|
| 6 |
+
"system": {
|
| 7 |
+
"platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O",
|
| 8 |
+
"processor": "arm",
|
| 9 |
+
"python_version": "3.14.6",
|
| 10 |
+
"machine": "arm64",
|
| 11 |
+
"mlx_version": "0.32.0",
|
| 12 |
+
"mlx_vlm_version": "0.6.6",
|
| 13 |
+
"chip": "Apple M3 Max",
|
| 14 |
+
"total_memory_gb": 128.0
|
| 15 |
+
},
|
| 16 |
+
"model_load_time_seconds": 5.561058167004376,
|
| 17 |
+
"memory_after_load_mb": 3994.34375,
|
| 18 |
+
"num_warmup": 1,
|
| 19 |
+
"num_runs": 3,
|
| 20 |
+
"mean_tps": 312.7284719764298,
|
| 21 |
+
"std_tps": 0.9770038029028864,
|
| 22 |
+
"mean_elapsed_seconds": 1.2253679166703175,
|
| 23 |
+
"mean_peak_memory_mb": 5165.991419999999,
|
| 24 |
+
"runs": [
|
| 25 |
+
{
|
| 26 |
+
"run": 1,
|
| 27 |
+
"elapsed_seconds": 1.226103625012911,
|
| 28 |
+
"tokens_generated": 256,
|
| 29 |
+
"tokens_generated_source": "mlx-vlm token count",
|
| 30 |
+
"tokens_per_second": 311.86183520486986,
|
| 31 |
+
"tokens_per_second_source": "mlx-vlm generation_tps",
|
| 32 |
+
"peak_memory_mb": 5165.81578,
|
| 33 |
+
"memory_delta_mb": 1337.186768,
|
| 34 |
+
"process_peak_rss_mb": 4015.96875,
|
| 35 |
+
"prompt_tokens": 697,
|
| 36 |
+
"prompt_tokens_per_second": 1816.7391562081139,
|
| 37 |
+
"finish_reason": "length"
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"run": 2,
|
| 41 |
+
"elapsed_seconds": 1.2276696659973823,
|
| 42 |
+
"tokens_generated": 256,
|
| 43 |
+
"tokens_generated_source": "mlx-vlm token count",
|
| 44 |
+
"tokens_per_second": 312.2298514286067,
|
| 45 |
+
"tokens_per_second_source": "mlx-vlm generation_tps",
|
| 46 |
+
"peak_memory_mb": 5166.07924,
|
| 47 |
+
"memory_delta_mb": 1337.188084,
|
| 48 |
+
"process_peak_rss_mb": 4022.953125,
|
| 49 |
+
"prompt_tokens": 697,
|
| 50 |
+
"prompt_tokens_per_second": 1801.9665867511062,
|
| 51 |
+
"finish_reason": "length"
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"run": 3,
|
| 55 |
+
"elapsed_seconds": 1.2223304590006592,
|
| 56 |
+
"tokens_generated": 256,
|
| 57 |
+
"tokens_generated_source": "mlx-vlm token count",
|
| 58 |
+
"tokens_per_second": 314.0937292958128,
|
| 59 |
+
"tokens_per_second_source": "mlx-vlm generation_tps",
|
| 60 |
+
"peak_memory_mb": 5166.07924,
|
| 61 |
+
"memory_delta_mb": 1337.188084,
|
| 62 |
+
"process_peak_rss_mb": 4023.265625,
|
| 63 |
+
"prompt_tokens": 697,
|
| 64 |
+
"prompt_tokens_per_second": 1800.5791804452258,
|
| 65 |
+
"finish_reason": "length"
|
| 66 |
+
}
|
| 67 |
+
]
|
| 68 |
+
}
|
release/release_manifest.json
ADDED
|
@@ -0,0 +1,1191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema_version": 1,
|
| 3 |
+
"created_at": "2026-07-20T23:31:41.909496+00:00",
|
| 4 |
+
"release_approved": true,
|
| 5 |
+
"repo_id": "AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8",
|
| 6 |
+
"source": {
|
| 7 |
+
"id": "baidu/Unlimited-OCR",
|
| 8 |
+
"weights": {
|
| 9 |
+
"files": [
|
| 10 |
+
{
|
| 11 |
+
"name": "model-00001-of-000001.safetensors",
|
| 12 |
+
"size": 6672547120,
|
| 13 |
+
"sha256": "2bc48a7a110061ea58fff65d3169367eebe3aee371ca6968dc2219c1b2855fc6"
|
| 14 |
+
}
|
| 15 |
+
],
|
| 16 |
+
"total_size_bytes": 6672547120,
|
| 17 |
+
"total_size_gb": 6.214293763041496,
|
| 18 |
+
"aggregate_sha256": "ede2e0c0a6261543dd0e2eb249c337a3409b2b69b8f9ed73fa604ac409bdf8f2"
|
| 19 |
+
}
|
| 20 |
+
},
|
| 21 |
+
"reference": {
|
| 22 |
+
"id": "sahilchachra/unlimited-ocr-mxfp8-mlx",
|
| 23 |
+
"weights": {
|
| 24 |
+
"files": [
|
| 25 |
+
{
|
| 26 |
+
"name": "model.safetensors",
|
| 27 |
+
"size": 3827646853,
|
| 28 |
+
"sha256": "eef3e920d4ef9429ef6353b4b98c34ed8935f678f91ff3bcf7de8ed73a7530db"
|
| 29 |
+
}
|
| 30 |
+
],
|
| 31 |
+
"total_size_bytes": 3827646853,
|
| 32 |
+
"total_size_gb": 3.564773921854794,
|
| 33 |
+
"aggregate_sha256": "439c6acfe5e277537dfe3368b94a145a6b0da4c39e4bd00582977d030f292ad1"
|
| 34 |
+
}
|
| 35 |
+
},
|
| 36 |
+
"candidate": {
|
| 37 |
+
"name": "AX-Unlimited-OCR-3B-MoE-MLX-MXFP8",
|
| 38 |
+
"weights": {
|
| 39 |
+
"files": [
|
| 40 |
+
{
|
| 41 |
+
"name": "model.safetensors",
|
| 42 |
+
"size": 3843160559,
|
| 43 |
+
"sha256": "27568517565832e42daa563f6939e66a8ebb50cce0b01a24ecebb3ea6b4d0b10"
|
| 44 |
+
}
|
| 45 |
+
],
|
| 46 |
+
"total_size_bytes": 3843160559,
|
| 47 |
+
"total_size_gb": 3.5792221864685416,
|
| 48 |
+
"aggregate_sha256": "a4f6e9934f90564906f42929542ad68493f0c6187ad431128e447debbf52b6f4"
|
| 49 |
+
},
|
| 50 |
+
"metadata": {
|
| 51 |
+
"passed": true,
|
| 52 |
+
"checks": {
|
| 53 |
+
"architecture": true,
|
| 54 |
+
"model_type": true,
|
| 55 |
+
"mxfp8": true,
|
| 56 |
+
"sliding_window": true,
|
| 57 |
+
"processor_class": true,
|
| 58 |
+
"sft_format": true
|
| 59 |
+
},
|
| 60 |
+
"sliding_window": 128
|
| 61 |
+
}
|
| 62 |
+
},
|
| 63 |
+
"dataset": {
|
| 64 |
+
"name": "eval_dataset",
|
| 65 |
+
"num_samples": 12,
|
| 66 |
+
"num_digit_samples": 12,
|
| 67 |
+
"num_cjk_samples": 3,
|
| 68 |
+
"num_table_samples": 3,
|
| 69 |
+
"category_counts": {
|
| 70 |
+
"cjk_document": 3,
|
| 71 |
+
"english_document": 4,
|
| 72 |
+
"low_quality_scan": 2,
|
| 73 |
+
"table_numeric": 3
|
| 74 |
+
},
|
| 75 |
+
"manifest_sha256": "94d96ec344b02b8c4b413eafe524d6844bddcb00705af4b3fccea4c297c22509",
|
| 76 |
+
"content_sha256": "78ca3e7c9c7584aea30b04176716a6dadf49ccf0f2bead959cbaeb9aeac68f30",
|
| 77 |
+
"files": [
|
| 78 |
+
{
|
| 79 |
+
"path": "images/0001.png",
|
| 80 |
+
"size": 243645,
|
| 81 |
+
"sha256": "8fad74c7e8f314075f34d5b3b3e6272a2c22a90e960dcf8074039b1f065895b4"
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"path": "ground_truth/0001.txt",
|
| 85 |
+
"size": 1346,
|
| 86 |
+
"sha256": "a6f35a418836e8ba0667dc42d663219b07b65cbbf7a4627b2319f0fa44d11f8e"
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
"path": "images/0002.png",
|
| 90 |
+
"size": 208699,
|
| 91 |
+
"sha256": "31f2d9c651112e963366e5e39f3da4ff8198ec41e2187c3c8fb5fec0edd2f64c"
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"path": "ground_truth/0002.txt",
|
| 95 |
+
"size": 838,
|
| 96 |
+
"sha256": "a05196b747f6898102fc56e23cae31c32754b985bc3eab0abe60d955bfadff68"
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"path": "images/0003.png",
|
| 100 |
+
"size": 181040,
|
| 101 |
+
"sha256": "40330138b25a7ce9bf142a672ad0a6253517bf076af66591c4431490eff1ecfc"
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"path": "ground_truth/0003.txt",
|
| 105 |
+
"size": 1333,
|
| 106 |
+
"sha256": "517ab5af1c090227d4dc2473ac422ecd9301e2e21ba0cc1c54d08476b1903d7e"
|
| 107 |
+
},
|
| 108 |
+
{
|
| 109 |
+
"path": "images/0004.png",
|
| 110 |
+
"size": 220208,
|
| 111 |
+
"sha256": "4316ba5ebe86c62331361e6d23329adede5e9eecf50a70db16b5439d95f5e23e"
|
| 112 |
+
},
|
| 113 |
+
{
|
| 114 |
+
"path": "ground_truth/0004.txt",
|
| 115 |
+
"size": 1330,
|
| 116 |
+
"sha256": "df3b4afaee0f3ffe0984c2f1d1201c1762bc70f4ffbc1bfe9b1242e17d794148"
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"path": "images/0005.png",
|
| 120 |
+
"size": 38984,
|
| 121 |
+
"sha256": "1532b86fe28d7331379c73b62cfb0b0ae1448824566684021df262e4741b06a4"
|
| 122 |
+
},
|
| 123 |
+
{
|
| 124 |
+
"path": "ground_truth/0005.txt",
|
| 125 |
+
"size": 449,
|
| 126 |
+
"sha256": "fb8ebea089dacff057d98feb6450436f3f6db65f0bac29513d3417e94451686e"
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"path": "images/0006.png",
|
| 130 |
+
"size": 38512,
|
| 131 |
+
"sha256": "8c1bf51f86f0f22c8898cec8c77049b6f023b77aab77f55be40f75b477ab6383"
|
| 132 |
+
},
|
| 133 |
+
{
|
| 134 |
+
"path": "ground_truth/0006.txt",
|
| 135 |
+
"size": 449,
|
| 136 |
+
"sha256": "f60ff098ac431b1021fb85f810dd6264c1c021fab0810ab97ce304c5eb802598"
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"path": "images/0007.png",
|
| 140 |
+
"size": 38911,
|
| 141 |
+
"sha256": "a87e7f53196e9d3f72af611394297ec64e9eb222d2872b62f0c5a2014d83ecfc"
|
| 142 |
+
},
|
| 143 |
+
{
|
| 144 |
+
"path": "ground_truth/0007.txt",
|
| 145 |
+
"size": 449,
|
| 146 |
+
"sha256": "0381ec16f8895290dfc2b81f97933f5568a68bfe731cc4791272ff8269e57c0e"
|
| 147 |
+
},
|
| 148 |
+
{
|
| 149 |
+
"path": "images/0008.png",
|
| 150 |
+
"size": 45665,
|
| 151 |
+
"sha256": "d9ff0cc9ffa639e485b0d445e1f4856f479ed9e2e485b1ca1ece9a9a043bc6c8"
|
| 152 |
+
},
|
| 153 |
+
{
|
| 154 |
+
"path": "ground_truth/0008.txt",
|
| 155 |
+
"size": 185,
|
| 156 |
+
"sha256": "2dbeb40c0172dc618c7298a87e131153f51682dcaa33efefa67b7e50ecd7a8d1"
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
"path": "images/0009.png",
|
| 160 |
+
"size": 40784,
|
| 161 |
+
"sha256": "f93ea216d77bfdb23f6c8c113e33c946318d6eac6ef3bdaf2e2f2c0d86c154c9"
|
| 162 |
+
},
|
| 163 |
+
{
|
| 164 |
+
"path": "ground_truth/0009.txt",
|
| 165 |
+
"size": 188,
|
| 166 |
+
"sha256": "957232168770c2a15efeb33126a7d2d8141ce34782106b979e4b31acfa4f9bee"
|
| 167 |
+
},
|
| 168 |
+
{
|
| 169 |
+
"path": "images/0010.png",
|
| 170 |
+
"size": 20512,
|
| 171 |
+
"sha256": "5ff52fd340da20784c4c934cbac3ae9a0788b7d1654d71934100624842ddf764"
|
| 172 |
+
},
|
| 173 |
+
{
|
| 174 |
+
"path": "ground_truth/0010.txt",
|
| 175 |
+
"size": 194,
|
| 176 |
+
"sha256": "2f9054687e5eb369e35d985bc2fe9d2d37ffeb8cafe204c3e81830f4431f5793"
|
| 177 |
+
},
|
| 178 |
+
{
|
| 179 |
+
"path": "images/0011.png",
|
| 180 |
+
"size": 486656,
|
| 181 |
+
"sha256": "74e878d54295c224481f0f1b51a670459b855b16492c246c27636d71f4666e68"
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"path": "ground_truth/0011.txt",
|
| 185 |
+
"size": 119,
|
| 186 |
+
"sha256": "8f8bc16447a12c17e4036c4c5fd936faef227310a564de983ebe2f4f22742d83"
|
| 187 |
+
},
|
| 188 |
+
{
|
| 189 |
+
"path": "images/0012.png",
|
| 190 |
+
"size": 515472,
|
| 191 |
+
"sha256": "9a6e49af19e893000b8e96bece22fe99fedd3741e7ac3dede04a112ea77b5b5c"
|
| 192 |
+
},
|
| 193 |
+
{
|
| 194 |
+
"path": "ground_truth/0012.txt",
|
| 195 |
+
"size": 119,
|
| 196 |
+
"sha256": "1ccf00fe53da00ba3a2340dbe95e1692e87efccdc04b191c0f1d52dab23169a7"
|
| 197 |
+
}
|
| 198 |
+
]
|
| 199 |
+
},
|
| 200 |
+
"thresholds": {
|
| 201 |
+
"max_cer_delta_vs_bf16": 0.01,
|
| 202 |
+
"max_cer_delta_vs_reference": 0.005,
|
| 203 |
+
"max_digit_cer_delta_vs_bf16": 0.01,
|
| 204 |
+
"max_table_score_degradation_vs_bf16": 0.01,
|
| 205 |
+
"min_tps_ratio_vs_reference": 0.9,
|
| 206 |
+
"max_weight_size_gb": 4.5
|
| 207 |
+
},
|
| 208 |
+
"metrics": {
|
| 209 |
+
"bf16_accuracy": {
|
| 210 |
+
"model_path": "Unlimited-OCR",
|
| 211 |
+
"num_samples": 12,
|
| 212 |
+
"mean_cer": 0.2391877132924964,
|
| 213 |
+
"mean_digit_cer": 0.08733996757027829,
|
| 214 |
+
"mean_cjk_cer": 0.3333333333333333,
|
| 215 |
+
"mean_table_score": 1.0,
|
| 216 |
+
"max_tokens": 1024,
|
| 217 |
+
"profile": "accurate"
|
| 218 |
+
},
|
| 219 |
+
"reference_accuracy": {
|
| 220 |
+
"model_path": "sahilchachra/unlimited-ocr-mxfp8-mlx",
|
| 221 |
+
"num_samples": 12,
|
| 222 |
+
"mean_cer": 1.057248773567081,
|
| 223 |
+
"mean_digit_cer": 0.20915311607827292,
|
| 224 |
+
"mean_cjk_cer": 1.0,
|
| 225 |
+
"mean_table_score": 1.0,
|
| 226 |
+
"max_tokens": 1024,
|
| 227 |
+
"profile": "accurate"
|
| 228 |
+
},
|
| 229 |
+
"candidate_accuracy": {
|
| 230 |
+
"model_path": "AX-Unlimited-OCR-3B-MoE-MLX-MXFP8",
|
| 231 |
+
"num_samples": 12,
|
| 232 |
+
"mean_cer": 0.23738438172232493,
|
| 233 |
+
"mean_digit_cer": 0.08573253598528652,
|
| 234 |
+
"mean_cjk_cer": 0.3333333333333333,
|
| 235 |
+
"mean_table_score": 1.0,
|
| 236 |
+
"max_tokens": 1024,
|
| 237 |
+
"profile": "accurate"
|
| 238 |
+
},
|
| 239 |
+
"reference_performance": {
|
| 240 |
+
"model_path": "sahilchachra/unlimited-ocr-mxfp8-mlx",
|
| 241 |
+
"mean_tps": 312.7284719764298,
|
| 242 |
+
"mean_peak_memory_mb": 5165.991419999999,
|
| 243 |
+
"max_tokens": 256
|
| 244 |
+
},
|
| 245 |
+
"candidate_performance": {
|
| 246 |
+
"model_path": "AX-Unlimited-OCR-3B-MoE-MLX-MXFP8",
|
| 247 |
+
"mean_tps": 297.2042493840031,
|
| 248 |
+
"mean_peak_memory_mb": 5181.5234390000005,
|
| 249 |
+
"max_tokens": 256
|
| 250 |
+
}
|
| 251 |
+
},
|
| 252 |
+
"rswa": {
|
| 253 |
+
"model_path": "AX-Unlimited-OCR-3B-MoE-MLX-MXFP8",
|
| 254 |
+
"image_path": "test_invoice.png",
|
| 255 |
+
"prompt": "<image>document parsing.",
|
| 256 |
+
"force_min_tokens": 8192,
|
| 257 |
+
"forced_eos_token_ids": [
|
| 258 |
+
1
|
| 259 |
+
],
|
| 260 |
+
"platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O",
|
| 261 |
+
"processor": "arm",
|
| 262 |
+
"baseline_memory_mb": 3844.161032,
|
| 263 |
+
"test_results": [
|
| 264 |
+
{
|
| 265 |
+
"max_tokens": 512,
|
| 266 |
+
"tokens_generated": 512,
|
| 267 |
+
"tokens_generated_source": "mlx-vlm token count",
|
| 268 |
+
"tokens_generated_reliable": true,
|
| 269 |
+
"elapsed_seconds": 2.4322984160098713,
|
| 270 |
+
"mean_tps": 264.34124967086365,
|
| 271 |
+
"tps_source": "mlx-vlm generation_tps",
|
| 272 |
+
"peak_memory_mb": 5181.350593,
|
| 273 |
+
"memory_growth_mb": 1337.1895610000001,
|
| 274 |
+
"repetition_rate": 0.0,
|
| 275 |
+
"completed_naturally": false,
|
| 276 |
+
"finish_reason": "length",
|
| 277 |
+
"forced_minimum_tokens": 512,
|
| 278 |
+
"status": "success"
|
| 279 |
+
},
|
| 280 |
+
{
|
| 281 |
+
"max_tokens": 2048,
|
| 282 |
+
"tokens_generated": 2048,
|
| 283 |
+
"tokens_generated_source": "mlx-vlm token count",
|
| 284 |
+
"tokens_generated_reliable": true,
|
| 285 |
+
"elapsed_seconds": 8.234726583992597,
|
| 286 |
+
"mean_tps": 261.8525825264912,
|
| 287 |
+
"tps_source": "mlx-vlm generation_tps",
|
| 288 |
+
"peak_memory_mb": 5181.612737,
|
| 289 |
+
"memory_growth_mb": 1337.1895490000002,
|
| 290 |
+
"repetition_rate": 0.09565217391304348,
|
| 291 |
+
"completed_naturally": false,
|
| 292 |
+
"finish_reason": "length",
|
| 293 |
+
"forced_minimum_tokens": 2048,
|
| 294 |
+
"status": "success"
|
| 295 |
+
},
|
| 296 |
+
{
|
| 297 |
+
"max_tokens": 8192,
|
| 298 |
+
"tokens_generated": 8192,
|
| 299 |
+
"tokens_generated_source": "mlx-vlm token count",
|
| 300 |
+
"tokens_generated_reliable": true,
|
| 301 |
+
"elapsed_seconds": 31.729143417003797,
|
| 302 |
+
"mean_tps": 261.56365755570715,
|
| 303 |
+
"tps_source": "mlx-vlm generation_tps",
|
| 304 |
+
"peak_memory_mb": 5181.612737,
|
| 305 |
+
"memory_growth_mb": 1337.1895490000002,
|
| 306 |
+
"repetition_rate": 0.09565217391304348,
|
| 307 |
+
"completed_naturally": false,
|
| 308 |
+
"finish_reason": "length",
|
| 309 |
+
"forced_minimum_tokens": 8192,
|
| 310 |
+
"status": "success"
|
| 311 |
+
}
|
| 312 |
+
],
|
| 313 |
+
"analysis": {
|
| 314 |
+
"cache_appears_bounded": true,
|
| 315 |
+
"cache_analysis_reason": "Peak memory stayed within 64 MB across a >=4x token span",
|
| 316 |
+
"tps_stable_over_length": true,
|
| 317 |
+
"max_successful_tokens": 8192,
|
| 318 |
+
"max_repetition_rate": 0.09565217391304348
|
| 319 |
+
},
|
| 320 |
+
"pass_conditions": {
|
| 321 |
+
"cache_bounded": true,
|
| 322 |
+
"tps_stable": true,
|
| 323 |
+
"8k_test_passed": true
|
| 324 |
+
},
|
| 325 |
+
"passed": true
|
| 326 |
+
},
|
| 327 |
+
"sensitivity": {
|
| 328 |
+
"baseline": {
|
| 329 |
+
"num_samples": 12,
|
| 330 |
+
"num_digit_samples": 12,
|
| 331 |
+
"num_table_samples": 3,
|
| 332 |
+
"mean_cer": 0.2391877132924964,
|
| 333 |
+
"mean_digit_cer": 0.08733996757027829,
|
| 334 |
+
"mean_table_score": 1.0,
|
| 335 |
+
"total_inference_time": 37.96379516503657,
|
| 336 |
+
"avg_time_per_image": 3.163649597086381
|
| 337 |
+
},
|
| 338 |
+
"groups": {
|
| 339 |
+
"vision_encoder": {
|
| 340 |
+
"status": "success",
|
| 341 |
+
"matched_module_count": 145,
|
| 342 |
+
"matched_modules": [
|
| 343 |
+
"sam_model.blocks.0.attn.proj",
|
| 344 |
+
"sam_model.blocks.0.attn.qkv",
|
| 345 |
+
"sam_model.blocks.0.mlp.lin1",
|
| 346 |
+
"sam_model.blocks.0.mlp.lin2",
|
| 347 |
+
"sam_model.blocks.1.attn.proj",
|
| 348 |
+
"sam_model.blocks.1.attn.qkv",
|
| 349 |
+
"sam_model.blocks.1.mlp.lin1",
|
| 350 |
+
"sam_model.blocks.1.mlp.lin2",
|
| 351 |
+
"sam_model.blocks.10.attn.proj",
|
| 352 |
+
"sam_model.blocks.10.attn.qkv",
|
| 353 |
+
"sam_model.blocks.10.mlp.lin1",
|
| 354 |
+
"sam_model.blocks.10.mlp.lin2",
|
| 355 |
+
"sam_model.blocks.11.attn.proj",
|
| 356 |
+
"sam_model.blocks.11.attn.qkv",
|
| 357 |
+
"sam_model.blocks.11.mlp.lin1",
|
| 358 |
+
"sam_model.blocks.11.mlp.lin2",
|
| 359 |
+
"sam_model.blocks.2.attn.proj",
|
| 360 |
+
"sam_model.blocks.2.attn.qkv",
|
| 361 |
+
"sam_model.blocks.2.mlp.lin1",
|
| 362 |
+
"sam_model.blocks.2.mlp.lin2",
|
| 363 |
+
"sam_model.blocks.3.attn.proj",
|
| 364 |
+
"sam_model.blocks.3.attn.qkv",
|
| 365 |
+
"sam_model.blocks.3.mlp.lin1",
|
| 366 |
+
"sam_model.blocks.3.mlp.lin2",
|
| 367 |
+
"sam_model.blocks.4.attn.proj",
|
| 368 |
+
"sam_model.blocks.4.attn.qkv",
|
| 369 |
+
"sam_model.blocks.4.mlp.lin1",
|
| 370 |
+
"sam_model.blocks.4.mlp.lin2",
|
| 371 |
+
"sam_model.blocks.5.attn.proj",
|
| 372 |
+
"sam_model.blocks.5.attn.qkv",
|
| 373 |
+
"sam_model.blocks.5.mlp.lin1",
|
| 374 |
+
"sam_model.blocks.5.mlp.lin2",
|
| 375 |
+
"sam_model.blocks.6.attn.proj",
|
| 376 |
+
"sam_model.blocks.6.attn.qkv",
|
| 377 |
+
"sam_model.blocks.6.mlp.lin1",
|
| 378 |
+
"sam_model.blocks.6.mlp.lin2",
|
| 379 |
+
"sam_model.blocks.7.attn.proj",
|
| 380 |
+
"sam_model.blocks.7.attn.qkv",
|
| 381 |
+
"sam_model.blocks.7.mlp.lin1",
|
| 382 |
+
"sam_model.blocks.7.mlp.lin2",
|
| 383 |
+
"sam_model.blocks.8.attn.proj",
|
| 384 |
+
"sam_model.blocks.8.attn.qkv",
|
| 385 |
+
"sam_model.blocks.8.mlp.lin1",
|
| 386 |
+
"sam_model.blocks.8.mlp.lin2",
|
| 387 |
+
"sam_model.blocks.9.attn.proj",
|
| 388 |
+
"sam_model.blocks.9.attn.qkv",
|
| 389 |
+
"sam_model.blocks.9.mlp.lin1",
|
| 390 |
+
"sam_model.blocks.9.mlp.lin2",
|
| 391 |
+
"vision_model.embeddings.position_embedding",
|
| 392 |
+
"vision_model.transformer.layers.0.mlp.fc1",
|
| 393 |
+
"vision_model.transformer.layers.0.mlp.fc2",
|
| 394 |
+
"vision_model.transformer.layers.0.self_attn.out_proj",
|
| 395 |
+
"vision_model.transformer.layers.0.self_attn.qkv_proj",
|
| 396 |
+
"vision_model.transformer.layers.1.mlp.fc1",
|
| 397 |
+
"vision_model.transformer.layers.1.mlp.fc2",
|
| 398 |
+
"vision_model.transformer.layers.1.self_attn.out_proj",
|
| 399 |
+
"vision_model.transformer.layers.1.self_attn.qkv_proj",
|
| 400 |
+
"vision_model.transformer.layers.10.mlp.fc1",
|
| 401 |
+
"vision_model.transformer.layers.10.mlp.fc2",
|
| 402 |
+
"vision_model.transformer.layers.10.self_attn.out_proj",
|
| 403 |
+
"vision_model.transformer.layers.10.self_attn.qkv_proj",
|
| 404 |
+
"vision_model.transformer.layers.11.mlp.fc1",
|
| 405 |
+
"vision_model.transformer.layers.11.mlp.fc2",
|
| 406 |
+
"vision_model.transformer.layers.11.self_attn.out_proj",
|
| 407 |
+
"vision_model.transformer.layers.11.self_attn.qkv_proj",
|
| 408 |
+
"vision_model.transformer.layers.12.mlp.fc1",
|
| 409 |
+
"vision_model.transformer.layers.12.mlp.fc2",
|
| 410 |
+
"vision_model.transformer.layers.12.self_attn.out_proj",
|
| 411 |
+
"vision_model.transformer.layers.12.self_attn.qkv_proj",
|
| 412 |
+
"vision_model.transformer.layers.13.mlp.fc1",
|
| 413 |
+
"vision_model.transformer.layers.13.mlp.fc2",
|
| 414 |
+
"vision_model.transformer.layers.13.self_attn.out_proj",
|
| 415 |
+
"vision_model.transformer.layers.13.self_attn.qkv_proj",
|
| 416 |
+
"vision_model.transformer.layers.14.mlp.fc1",
|
| 417 |
+
"vision_model.transformer.layers.14.mlp.fc2",
|
| 418 |
+
"vision_model.transformer.layers.14.self_attn.out_proj",
|
| 419 |
+
"vision_model.transformer.layers.14.self_attn.qkv_proj",
|
| 420 |
+
"vision_model.transformer.layers.15.mlp.fc1",
|
| 421 |
+
"vision_model.transformer.layers.15.mlp.fc2",
|
| 422 |
+
"vision_model.transformer.layers.15.self_attn.out_proj",
|
| 423 |
+
"vision_model.transformer.layers.15.self_attn.qkv_proj",
|
| 424 |
+
"vision_model.transformer.layers.16.mlp.fc1",
|
| 425 |
+
"vision_model.transformer.layers.16.mlp.fc2",
|
| 426 |
+
"vision_model.transformer.layers.16.self_attn.out_proj",
|
| 427 |
+
"vision_model.transformer.layers.16.self_attn.qkv_proj",
|
| 428 |
+
"vision_model.transformer.layers.17.mlp.fc1",
|
| 429 |
+
"vision_model.transformer.layers.17.mlp.fc2",
|
| 430 |
+
"vision_model.transformer.layers.17.self_attn.out_proj",
|
| 431 |
+
"vision_model.transformer.layers.17.self_attn.qkv_proj",
|
| 432 |
+
"vision_model.transformer.layers.18.mlp.fc1",
|
| 433 |
+
"vision_model.transformer.layers.18.mlp.fc2",
|
| 434 |
+
"vision_model.transformer.layers.18.self_attn.out_proj",
|
| 435 |
+
"vision_model.transformer.layers.18.self_attn.qkv_proj",
|
| 436 |
+
"vision_model.transformer.layers.19.mlp.fc1",
|
| 437 |
+
"vision_model.transformer.layers.19.mlp.fc2",
|
| 438 |
+
"vision_model.transformer.layers.19.self_attn.out_proj",
|
| 439 |
+
"vision_model.transformer.layers.19.self_attn.qkv_proj",
|
| 440 |
+
"vision_model.transformer.layers.2.mlp.fc1",
|
| 441 |
+
"vision_model.transformer.layers.2.mlp.fc2",
|
| 442 |
+
"vision_model.transformer.layers.2.self_attn.out_proj",
|
| 443 |
+
"vision_model.transformer.layers.2.self_attn.qkv_proj",
|
| 444 |
+
"vision_model.transformer.layers.20.mlp.fc1",
|
| 445 |
+
"vision_model.transformer.layers.20.mlp.fc2",
|
| 446 |
+
"vision_model.transformer.layers.20.self_attn.out_proj",
|
| 447 |
+
"vision_model.transformer.layers.20.self_attn.qkv_proj",
|
| 448 |
+
"vision_model.transformer.layers.21.mlp.fc1",
|
| 449 |
+
"vision_model.transformer.layers.21.mlp.fc2",
|
| 450 |
+
"vision_model.transformer.layers.21.self_attn.out_proj",
|
| 451 |
+
"vision_model.transformer.layers.21.self_attn.qkv_proj",
|
| 452 |
+
"vision_model.transformer.layers.22.mlp.fc1",
|
| 453 |
+
"vision_model.transformer.layers.22.mlp.fc2",
|
| 454 |
+
"vision_model.transformer.layers.22.self_attn.out_proj",
|
| 455 |
+
"vision_model.transformer.layers.22.self_attn.qkv_proj",
|
| 456 |
+
"vision_model.transformer.layers.23.mlp.fc1",
|
| 457 |
+
"vision_model.transformer.layers.23.mlp.fc2",
|
| 458 |
+
"vision_model.transformer.layers.23.self_attn.out_proj",
|
| 459 |
+
"vision_model.transformer.layers.23.self_attn.qkv_proj",
|
| 460 |
+
"vision_model.transformer.layers.3.mlp.fc1",
|
| 461 |
+
"vision_model.transformer.layers.3.mlp.fc2",
|
| 462 |
+
"vision_model.transformer.layers.3.self_attn.out_proj",
|
| 463 |
+
"vision_model.transformer.layers.3.self_attn.qkv_proj",
|
| 464 |
+
"vision_model.transformer.layers.4.mlp.fc1",
|
| 465 |
+
"vision_model.transformer.layers.4.mlp.fc2",
|
| 466 |
+
"vision_model.transformer.layers.4.self_attn.out_proj",
|
| 467 |
+
"vision_model.transformer.layers.4.self_attn.qkv_proj",
|
| 468 |
+
"vision_model.transformer.layers.5.mlp.fc1",
|
| 469 |
+
"vision_model.transformer.layers.5.mlp.fc2",
|
| 470 |
+
"vision_model.transformer.layers.5.self_attn.out_proj",
|
| 471 |
+
"vision_model.transformer.layers.5.self_attn.qkv_proj",
|
| 472 |
+
"vision_model.transformer.layers.6.mlp.fc1",
|
| 473 |
+
"vision_model.transformer.layers.6.mlp.fc2",
|
| 474 |
+
"vision_model.transformer.layers.6.self_attn.out_proj",
|
| 475 |
+
"vision_model.transformer.layers.6.self_attn.qkv_proj",
|
| 476 |
+
"vision_model.transformer.layers.7.mlp.fc1",
|
| 477 |
+
"vision_model.transformer.layers.7.mlp.fc2",
|
| 478 |
+
"vision_model.transformer.layers.7.self_attn.out_proj",
|
| 479 |
+
"vision_model.transformer.layers.7.self_attn.qkv_proj",
|
| 480 |
+
"vision_model.transformer.layers.8.mlp.fc1",
|
| 481 |
+
"vision_model.transformer.layers.8.mlp.fc2",
|
| 482 |
+
"vision_model.transformer.layers.8.self_attn.out_proj",
|
| 483 |
+
"vision_model.transformer.layers.8.self_attn.qkv_proj",
|
| 484 |
+
"vision_model.transformer.layers.9.mlp.fc1",
|
| 485 |
+
"vision_model.transformer.layers.9.mlp.fc2",
|
| 486 |
+
"vision_model.transformer.layers.9.self_attn.out_proj",
|
| 487 |
+
"vision_model.transformer.layers.9.self_attn.qkv_proj"
|
| 488 |
+
],
|
| 489 |
+
"metrics": {
|
| 490 |
+
"num_samples": 12,
|
| 491 |
+
"num_digit_samples": 12,
|
| 492 |
+
"num_table_samples": 3,
|
| 493 |
+
"mean_cer": 2.6628739674476654,
|
| 494 |
+
"mean_digit_cer": 1.445363129900987,
|
| 495 |
+
"mean_table_score": 0.0,
|
| 496 |
+
"total_inference_time": 82.4734385000047,
|
| 497 |
+
"avg_time_per_image": 6.872786541667058
|
| 498 |
+
},
|
| 499 |
+
"cer_delta": 2.423686254155169,
|
| 500 |
+
"digit_cer_delta": 1.3580231623307086,
|
| 501 |
+
"table_score_degradation": 1.0,
|
| 502 |
+
"sensitivity_rank": 1
|
| 503 |
+
},
|
| 504 |
+
"vision_projector": {
|
| 505 |
+
"status": "success",
|
| 506 |
+
"matched_module_count": 1,
|
| 507 |
+
"matched_modules": [
|
| 508 |
+
"projector.layers"
|
| 509 |
+
],
|
| 510 |
+
"metrics": {
|
| 511 |
+
"num_samples": 12,
|
| 512 |
+
"num_digit_samples": 12,
|
| 513 |
+
"num_table_samples": 3,
|
| 514 |
+
"mean_cer": 0.23591319611894124,
|
| 515 |
+
"mean_digit_cer": 0.08425354781719185,
|
| 516 |
+
"mean_table_score": 1.0,
|
| 517 |
+
"total_inference_time": 37.98282545800612,
|
| 518 |
+
"avg_time_per_image": 3.165235454833843
|
| 519 |
+
},
|
| 520 |
+
"cer_delta": -0.003274517173555175,
|
| 521 |
+
"digit_cer_delta": -0.0030864197530864335,
|
| 522 |
+
"table_score_degradation": 0.0,
|
| 523 |
+
"sensitivity_rank": 7
|
| 524 |
+
},
|
| 525 |
+
"token_embeddings": {
|
| 526 |
+
"status": "success",
|
| 527 |
+
"matched_module_count": 1,
|
| 528 |
+
"matched_modules": [
|
| 529 |
+
"language_model.model.embed_tokens"
|
| 530 |
+
],
|
| 531 |
+
"metrics": {
|
| 532 |
+
"num_samples": 12,
|
| 533 |
+
"num_digit_samples": 12,
|
| 534 |
+
"num_table_samples": 3,
|
| 535 |
+
"mean_cer": 0.2326891302977425,
|
| 536 |
+
"mean_digit_cer": 0.08249916185227958,
|
| 537 |
+
"mean_table_score": 1.0,
|
| 538 |
+
"total_inference_time": 38.66650962494896,
|
| 539 |
+
"avg_time_per_image": 3.222209135412413
|
| 540 |
+
},
|
| 541 |
+
"cer_delta": -0.006498582994753921,
|
| 542 |
+
"digit_cer_delta": -0.004840805717998706,
|
| 543 |
+
"table_score_degradation": 0.0,
|
| 544 |
+
"sensitivity_rank": 10
|
| 545 |
+
},
|
| 546 |
+
"attention_q_proj": {
|
| 547 |
+
"status": "success",
|
| 548 |
+
"matched_module_count": 12,
|
| 549 |
+
"matched_modules": [
|
| 550 |
+
"language_model.model.layers.0.self_attn.q_proj",
|
| 551 |
+
"language_model.model.layers.1.self_attn.q_proj",
|
| 552 |
+
"language_model.model.layers.10.self_attn.q_proj",
|
| 553 |
+
"language_model.model.layers.11.self_attn.q_proj",
|
| 554 |
+
"language_model.model.layers.2.self_attn.q_proj",
|
| 555 |
+
"language_model.model.layers.3.self_attn.q_proj",
|
| 556 |
+
"language_model.model.layers.4.self_attn.q_proj",
|
| 557 |
+
"language_model.model.layers.5.self_attn.q_proj",
|
| 558 |
+
"language_model.model.layers.6.self_attn.q_proj",
|
| 559 |
+
"language_model.model.layers.7.self_attn.q_proj",
|
| 560 |
+
"language_model.model.layers.8.self_attn.q_proj",
|
| 561 |
+
"language_model.model.layers.9.self_attn.q_proj"
|
| 562 |
+
],
|
| 563 |
+
"metrics": {
|
| 564 |
+
"num_samples": 12,
|
| 565 |
+
"num_digit_samples": 12,
|
| 566 |
+
"num_table_samples": 3,
|
| 567 |
+
"mean_cer": 0.21507560490000377,
|
| 568 |
+
"mean_digit_cer": 0.09653424957157782,
|
| 569 |
+
"mean_table_score": 1.0,
|
| 570 |
+
"total_inference_time": 38.38538125100604,
|
| 571 |
+
"avg_time_per_image": 3.19878177091717
|
| 572 |
+
},
|
| 573 |
+
"cer_delta": -0.024112108392492643,
|
| 574 |
+
"digit_cer_delta": 0.009194282001299528,
|
| 575 |
+
"table_score_degradation": 0.0,
|
| 576 |
+
"sensitivity_rank": 11
|
| 577 |
+
},
|
| 578 |
+
"attention_k_proj": {
|
| 579 |
+
"status": "success",
|
| 580 |
+
"matched_module_count": 12,
|
| 581 |
+
"matched_modules": [
|
| 582 |
+
"language_model.model.layers.0.self_attn.k_proj",
|
| 583 |
+
"language_model.model.layers.1.self_attn.k_proj",
|
| 584 |
+
"language_model.model.layers.10.self_attn.k_proj",
|
| 585 |
+
"language_model.model.layers.11.self_attn.k_proj",
|
| 586 |
+
"language_model.model.layers.2.self_attn.k_proj",
|
| 587 |
+
"language_model.model.layers.3.self_attn.k_proj",
|
| 588 |
+
"language_model.model.layers.4.self_attn.k_proj",
|
| 589 |
+
"language_model.model.layers.5.self_attn.k_proj",
|
| 590 |
+
"language_model.model.layers.6.self_attn.k_proj",
|
| 591 |
+
"language_model.model.layers.7.self_attn.k_proj",
|
| 592 |
+
"language_model.model.layers.8.self_attn.k_proj",
|
| 593 |
+
"language_model.model.layers.9.self_attn.k_proj"
|
| 594 |
+
],
|
| 595 |
+
"metrics": {
|
| 596 |
+
"num_samples": 12,
|
| 597 |
+
"num_digit_samples": 12,
|
| 598 |
+
"num_table_samples": 3,
|
| 599 |
+
"mean_cer": 0.23424214947533464,
|
| 600 |
+
"mean_digit_cer": 0.08425354781719185,
|
| 601 |
+
"mean_table_score": 1.0,
|
| 602 |
+
"total_inference_time": 38.175402873021085,
|
| 603 |
+
"avg_time_per_image": 3.181283572751757
|
| 604 |
+
},
|
| 605 |
+
"cer_delta": -0.004945563817161769,
|
| 606 |
+
"digit_cer_delta": -0.0030864197530864335,
|
| 607 |
+
"table_score_degradation": 0.0,
|
| 608 |
+
"sensitivity_rank": 9
|
| 609 |
+
},
|
| 610 |
+
"attention_v_proj": {
|
| 611 |
+
"status": "success",
|
| 612 |
+
"matched_module_count": 12,
|
| 613 |
+
"matched_modules": [
|
| 614 |
+
"language_model.model.layers.0.self_attn.v_proj",
|
| 615 |
+
"language_model.model.layers.1.self_attn.v_proj",
|
| 616 |
+
"language_model.model.layers.10.self_attn.v_proj",
|
| 617 |
+
"language_model.model.layers.11.self_attn.v_proj",
|
| 618 |
+
"language_model.model.layers.2.self_attn.v_proj",
|
| 619 |
+
"language_model.model.layers.3.self_attn.v_proj",
|
| 620 |
+
"language_model.model.layers.4.self_attn.v_proj",
|
| 621 |
+
"language_model.model.layers.5.self_attn.v_proj",
|
| 622 |
+
"language_model.model.layers.6.self_attn.v_proj",
|
| 623 |
+
"language_model.model.layers.7.self_attn.v_proj",
|
| 624 |
+
"language_model.model.layers.8.self_attn.v_proj",
|
| 625 |
+
"language_model.model.layers.9.self_attn.v_proj"
|
| 626 |
+
],
|
| 627 |
+
"metrics": {
|
| 628 |
+
"num_samples": 12,
|
| 629 |
+
"num_digit_samples": 12,
|
| 630 |
+
"num_table_samples": 3,
|
| 631 |
+
"mean_cer": 0.23642534441989824,
|
| 632 |
+
"mean_digit_cer": 0.08425354781719185,
|
| 633 |
+
"mean_table_score": 1.0,
|
| 634 |
+
"total_inference_time": 41.72621229101787,
|
| 635 |
+
"avg_time_per_image": 3.477184357584823
|
| 636 |
+
},
|
| 637 |
+
"cer_delta": -0.0027623688725981754,
|
| 638 |
+
"digit_cer_delta": -0.0030864197530864335,
|
| 639 |
+
"table_score_degradation": 0.0,
|
| 640 |
+
"sensitivity_rank": 5
|
| 641 |
+
},
|
| 642 |
+
"attention_o_proj": {
|
| 643 |
+
"status": "success",
|
| 644 |
+
"matched_module_count": 12,
|
| 645 |
+
"matched_modules": [
|
| 646 |
+
"language_model.model.layers.0.self_attn.o_proj",
|
| 647 |
+
"language_model.model.layers.1.self_attn.o_proj",
|
| 648 |
+
"language_model.model.layers.10.self_attn.o_proj",
|
| 649 |
+
"language_model.model.layers.11.self_attn.o_proj",
|
| 650 |
+
"language_model.model.layers.2.self_attn.o_proj",
|
| 651 |
+
"language_model.model.layers.3.self_attn.o_proj",
|
| 652 |
+
"language_model.model.layers.4.self_attn.o_proj",
|
| 653 |
+
"language_model.model.layers.5.self_attn.o_proj",
|
| 654 |
+
"language_model.model.layers.6.self_attn.o_proj",
|
| 655 |
+
"language_model.model.layers.7.self_attn.o_proj",
|
| 656 |
+
"language_model.model.layers.8.self_attn.o_proj",
|
| 657 |
+
"language_model.model.layers.9.self_attn.o_proj"
|
| 658 |
+
],
|
| 659 |
+
"metrics": {
|
| 660 |
+
"num_samples": 12,
|
| 661 |
+
"num_digit_samples": 12,
|
| 662 |
+
"num_table_samples": 3,
|
| 663 |
+
"mean_cer": 0.2359826017166606,
|
| 664 |
+
"mean_digit_cer": 0.07714420469193685,
|
| 665 |
+
"mean_table_score": 1.0,
|
| 666 |
+
"total_inference_time": 41.9442965429771,
|
| 667 |
+
"avg_time_per_image": 3.4953580452480915
|
| 668 |
+
},
|
| 669 |
+
"cer_delta": -0.003205111575835806,
|
| 670 |
+
"digit_cer_delta": -0.010195762878341438,
|
| 671 |
+
"table_score_degradation": 0.0,
|
| 672 |
+
"sensitivity_rank": 6
|
| 673 |
+
},
|
| 674 |
+
"dense_mlp": {
|
| 675 |
+
"status": "success",
|
| 676 |
+
"matched_module_count": 3,
|
| 677 |
+
"matched_modules": [
|
| 678 |
+
"language_model.model.layers.0.mlp.down_proj",
|
| 679 |
+
"language_model.model.layers.0.mlp.gate_proj",
|
| 680 |
+
"language_model.model.layers.0.mlp.up_proj"
|
| 681 |
+
],
|
| 682 |
+
"metrics": {
|
| 683 |
+
"num_samples": 12,
|
| 684 |
+
"num_digit_samples": 12,
|
| 685 |
+
"num_table_samples": 3,
|
| 686 |
+
"mean_cer": 0.23568756434998817,
|
| 687 |
+
"mean_digit_cer": 0.08425354781719185,
|
| 688 |
+
"mean_table_score": 1.0,
|
| 689 |
+
"total_inference_time": 41.424109207015135,
|
| 690 |
+
"avg_time_per_image": 3.4520091005845948
|
| 691 |
+
},
|
| 692 |
+
"cer_delta": -0.0035001489425082466,
|
| 693 |
+
"digit_cer_delta": -0.0030864197530864335,
|
| 694 |
+
"table_score_degradation": 0.0,
|
| 695 |
+
"sensitivity_rank": 8
|
| 696 |
+
},
|
| 697 |
+
"shared_experts": {
|
| 698 |
+
"status": "success",
|
| 699 |
+
"matched_module_count": 33,
|
| 700 |
+
"matched_modules": [
|
| 701 |
+
"language_model.model.layers.1.mlp.shared_experts.down_proj",
|
| 702 |
+
"language_model.model.layers.1.mlp.shared_experts.gate_proj",
|
| 703 |
+
"language_model.model.layers.1.mlp.shared_experts.up_proj",
|
| 704 |
+
"language_model.model.layers.10.mlp.shared_experts.down_proj",
|
| 705 |
+
"language_model.model.layers.10.mlp.shared_experts.gate_proj",
|
| 706 |
+
"language_model.model.layers.10.mlp.shared_experts.up_proj",
|
| 707 |
+
"language_model.model.layers.11.mlp.shared_experts.down_proj",
|
| 708 |
+
"language_model.model.layers.11.mlp.shared_experts.gate_proj",
|
| 709 |
+
"language_model.model.layers.11.mlp.shared_experts.up_proj",
|
| 710 |
+
"language_model.model.layers.2.mlp.shared_experts.down_proj",
|
| 711 |
+
"language_model.model.layers.2.mlp.shared_experts.gate_proj",
|
| 712 |
+
"language_model.model.layers.2.mlp.shared_experts.up_proj",
|
| 713 |
+
"language_model.model.layers.3.mlp.shared_experts.down_proj",
|
| 714 |
+
"language_model.model.layers.3.mlp.shared_experts.gate_proj",
|
| 715 |
+
"language_model.model.layers.3.mlp.shared_experts.up_proj",
|
| 716 |
+
"language_model.model.layers.4.mlp.shared_experts.down_proj",
|
| 717 |
+
"language_model.model.layers.4.mlp.shared_experts.gate_proj",
|
| 718 |
+
"language_model.model.layers.4.mlp.shared_experts.up_proj",
|
| 719 |
+
"language_model.model.layers.5.mlp.shared_experts.down_proj",
|
| 720 |
+
"language_model.model.layers.5.mlp.shared_experts.gate_proj",
|
| 721 |
+
"language_model.model.layers.5.mlp.shared_experts.up_proj",
|
| 722 |
+
"language_model.model.layers.6.mlp.shared_experts.down_proj",
|
| 723 |
+
"language_model.model.layers.6.mlp.shared_experts.gate_proj",
|
| 724 |
+
"language_model.model.layers.6.mlp.shared_experts.up_proj",
|
| 725 |
+
"language_model.model.layers.7.mlp.shared_experts.down_proj",
|
| 726 |
+
"language_model.model.layers.7.mlp.shared_experts.gate_proj",
|
| 727 |
+
"language_model.model.layers.7.mlp.shared_experts.up_proj",
|
| 728 |
+
"language_model.model.layers.8.mlp.shared_experts.down_proj",
|
| 729 |
+
"language_model.model.layers.8.mlp.shared_experts.gate_proj",
|
| 730 |
+
"language_model.model.layers.8.mlp.shared_experts.up_proj",
|
| 731 |
+
"language_model.model.layers.9.mlp.shared_experts.down_proj",
|
| 732 |
+
"language_model.model.layers.9.mlp.shared_experts.gate_proj",
|
| 733 |
+
"language_model.model.layers.9.mlp.shared_experts.up_proj"
|
| 734 |
+
],
|
| 735 |
+
"metrics": {
|
| 736 |
+
"num_samples": 12,
|
| 737 |
+
"num_digit_samples": 12,
|
| 738 |
+
"num_table_samples": 3,
|
| 739 |
+
"mean_cer": 0.23649792801080774,
|
| 740 |
+
"mean_digit_cer": 0.09592829886362793,
|
| 741 |
+
"mean_table_score": 1.0,
|
| 742 |
+
"total_inference_time": 37.84185174998129,
|
| 743 |
+
"avg_time_per_image": 3.153487645831774
|
| 744 |
+
},
|
| 745 |
+
"cer_delta": -0.0026897852816886725,
|
| 746 |
+
"digit_cer_delta": 0.00858833129334964,
|
| 747 |
+
"table_score_degradation": 0.0,
|
| 748 |
+
"sensitivity_rank": 4
|
| 749 |
+
},
|
| 750 |
+
"routed_experts": {
|
| 751 |
+
"status": "success",
|
| 752 |
+
"matched_module_count": 33,
|
| 753 |
+
"matched_modules": [
|
| 754 |
+
"language_model.model.layers.1.mlp.switch_mlp.down_proj",
|
| 755 |
+
"language_model.model.layers.1.mlp.switch_mlp.gate_proj",
|
| 756 |
+
"language_model.model.layers.1.mlp.switch_mlp.up_proj",
|
| 757 |
+
"language_model.model.layers.10.mlp.switch_mlp.down_proj",
|
| 758 |
+
"language_model.model.layers.10.mlp.switch_mlp.gate_proj",
|
| 759 |
+
"language_model.model.layers.10.mlp.switch_mlp.up_proj",
|
| 760 |
+
"language_model.model.layers.11.mlp.switch_mlp.down_proj",
|
| 761 |
+
"language_model.model.layers.11.mlp.switch_mlp.gate_proj",
|
| 762 |
+
"language_model.model.layers.11.mlp.switch_mlp.up_proj",
|
| 763 |
+
"language_model.model.layers.2.mlp.switch_mlp.down_proj",
|
| 764 |
+
"language_model.model.layers.2.mlp.switch_mlp.gate_proj",
|
| 765 |
+
"language_model.model.layers.2.mlp.switch_mlp.up_proj",
|
| 766 |
+
"language_model.model.layers.3.mlp.switch_mlp.down_proj",
|
| 767 |
+
"language_model.model.layers.3.mlp.switch_mlp.gate_proj",
|
| 768 |
+
"language_model.model.layers.3.mlp.switch_mlp.up_proj",
|
| 769 |
+
"language_model.model.layers.4.mlp.switch_mlp.down_proj",
|
| 770 |
+
"language_model.model.layers.4.mlp.switch_mlp.gate_proj",
|
| 771 |
+
"language_model.model.layers.4.mlp.switch_mlp.up_proj",
|
| 772 |
+
"language_model.model.layers.5.mlp.switch_mlp.down_proj",
|
| 773 |
+
"language_model.model.layers.5.mlp.switch_mlp.gate_proj",
|
| 774 |
+
"language_model.model.layers.5.mlp.switch_mlp.up_proj",
|
| 775 |
+
"language_model.model.layers.6.mlp.switch_mlp.down_proj",
|
| 776 |
+
"language_model.model.layers.6.mlp.switch_mlp.gate_proj",
|
| 777 |
+
"language_model.model.layers.6.mlp.switch_mlp.up_proj",
|
| 778 |
+
"language_model.model.layers.7.mlp.switch_mlp.down_proj",
|
| 779 |
+
"language_model.model.layers.7.mlp.switch_mlp.gate_proj",
|
| 780 |
+
"language_model.model.layers.7.mlp.switch_mlp.up_proj",
|
| 781 |
+
"language_model.model.layers.8.mlp.switch_mlp.down_proj",
|
| 782 |
+
"language_model.model.layers.8.mlp.switch_mlp.gate_proj",
|
| 783 |
+
"language_model.model.layers.8.mlp.switch_mlp.up_proj",
|
| 784 |
+
"language_model.model.layers.9.mlp.switch_mlp.down_proj",
|
| 785 |
+
"language_model.model.layers.9.mlp.switch_mlp.gate_proj",
|
| 786 |
+
"language_model.model.layers.9.mlp.switch_mlp.up_proj"
|
| 787 |
+
],
|
| 788 |
+
"metrics": {
|
| 789 |
+
"num_samples": 12,
|
| 790 |
+
"num_digit_samples": 12,
|
| 791 |
+
"num_table_samples": 3,
|
| 792 |
+
"mean_cer": 0.23911822127011442,
|
| 793 |
+
"mean_digit_cer": 0.08452479009169811,
|
| 794 |
+
"mean_table_score": 1.0,
|
| 795 |
+
"total_inference_time": 36.668351295011234,
|
| 796 |
+
"avg_time_per_image": 3.0556959412509364
|
| 797 |
+
},
|
| 798 |
+
"cer_delta": -6.949202238198948e-05,
|
| 799 |
+
"digit_cer_delta": -0.0028151774785801775,
|
| 800 |
+
"table_score_degradation": 0.0,
|
| 801 |
+
"sensitivity_rank": 3
|
| 802 |
+
},
|
| 803 |
+
"lm_head": {
|
| 804 |
+
"status": "success",
|
| 805 |
+
"matched_module_count": 1,
|
| 806 |
+
"matched_modules": [
|
| 807 |
+
"language_model.lm_head"
|
| 808 |
+
],
|
| 809 |
+
"metrics": {
|
| 810 |
+
"num_samples": 12,
|
| 811 |
+
"num_digit_samples": 12,
|
| 812 |
+
"num_table_samples": 3,
|
| 813 |
+
"mean_cer": 0.25282999056583605,
|
| 814 |
+
"mean_digit_cer": 0.12301897991595728,
|
| 815 |
+
"mean_table_score": 1.0,
|
| 816 |
+
"total_inference_time": 37.152046875009546,
|
| 817 |
+
"avg_time_per_image": 3.0960039062507954
|
| 818 |
+
},
|
| 819 |
+
"cer_delta": 0.013642277273339637,
|
| 820 |
+
"digit_cer_delta": 0.035679012345678995,
|
| 821 |
+
"table_score_degradation": 0.0,
|
| 822 |
+
"sensitivity_rank": 2
|
| 823 |
+
}
|
| 824 |
+
}
|
| 825 |
+
},
|
| 826 |
+
"calibration": {
|
| 827 |
+
"schema_version": 1,
|
| 828 |
+
"created_at": "2026-07-20T23:25:55.306169+00:00",
|
| 829 |
+
"target_pattern": "language_model.lm_head",
|
| 830 |
+
"selection_policy": "fastest candidate passing existing quality and throughput limits",
|
| 831 |
+
"thresholds": {
|
| 832 |
+
"max_cer_delta_vs_bf16": 0.01,
|
| 833 |
+
"max_cer_delta_vs_reference": 0.005,
|
| 834 |
+
"max_digit_cer_delta_vs_bf16": 0.01,
|
| 835 |
+
"max_table_score_degradation_vs_bf16": 0.01,
|
| 836 |
+
"min_tps_ratio_vs_reference": 0.9,
|
| 837 |
+
"max_weight_size_gb": 4.5
|
| 838 |
+
},
|
| 839 |
+
"experiments": [
|
| 840 |
+
{
|
| 841 |
+
"label": "bf16-head",
|
| 842 |
+
"precision": "bfloat16",
|
| 843 |
+
"passed": false,
|
| 844 |
+
"checks": {
|
| 845 |
+
"cer": true,
|
| 846 |
+
"digit_cer": true,
|
| 847 |
+
"table_score": true,
|
| 848 |
+
"throughput": false
|
| 849 |
+
},
|
| 850 |
+
"metrics": {
|
| 851 |
+
"mean_cer": 0.23190356458231498,
|
| 852 |
+
"mean_digit_cer": 0.08222376405546196,
|
| 853 |
+
"mean_table_score": 1.0,
|
| 854 |
+
"mean_tps": 271.0950821181683
|
| 855 |
+
},
|
| 856 |
+
"deltas": {
|
| 857 |
+
"cer_vs_bf16": -0.007284148710181432,
|
| 858 |
+
"digit_cer_vs_bf16": -0.005116203514816328,
|
| 859 |
+
"table_degradation_vs_bf16": 0.0,
|
| 860 |
+
"tps_ratio_vs_reference": 0.8668704848166195
|
| 861 |
+
}
|
| 862 |
+
},
|
| 863 |
+
{
|
| 864 |
+
"label": "mxfp8-head",
|
| 865 |
+
"precision": "mxfp8",
|
| 866 |
+
"passed": false,
|
| 867 |
+
"checks": {
|
| 868 |
+
"cer": false,
|
| 869 |
+
"digit_cer": true,
|
| 870 |
+
"table_score": true,
|
| 871 |
+
"throughput": true
|
| 872 |
+
},
|
| 873 |
+
"metrics": {
|
| 874 |
+
"mean_cer": 0.26017003915600867,
|
| 875 |
+
"mean_digit_cer": 0.0769606061607251,
|
| 876 |
+
"mean_table_score": 1.0,
|
| 877 |
+
"mean_tps": 307.3938836867251
|
| 878 |
+
},
|
| 879 |
+
"deltas": {
|
| 880 |
+
"cer_vs_bf16": 0.020982325863512258,
|
| 881 |
+
"digit_cer_vs_bf16": -0.010379361409553187,
|
| 882 |
+
"table_degradation_vs_bf16": 0.0,
|
| 883 |
+
"tps_ratio_vs_reference": 0.9829417889071937
|
| 884 |
+
}
|
| 885 |
+
},
|
| 886 |
+
{
|
| 887 |
+
"label": "affine8-head",
|
| 888 |
+
"precision": "affine8",
|
| 889 |
+
"passed": true,
|
| 890 |
+
"checks": {
|
| 891 |
+
"cer": true,
|
| 892 |
+
"digit_cer": true,
|
| 893 |
+
"table_score": true,
|
| 894 |
+
"throughput": true
|
| 895 |
+
},
|
| 896 |
+
"metrics": {
|
| 897 |
+
"mean_cer": 0.23738438172232493,
|
| 898 |
+
"mean_digit_cer": 0.08573253598528652,
|
| 899 |
+
"mean_table_score": 1.0,
|
| 900 |
+
"mean_tps": 301.24796539578256
|
| 901 |
+
},
|
| 902 |
+
"deltas": {
|
| 903 |
+
"cer_vs_bf16": -0.0018033315701714847,
|
| 904 |
+
"digit_cer_vs_bf16": -0.0016074315849917697,
|
| 905 |
+
"table_degradation_vs_bf16": 0.0,
|
| 906 |
+
"tps_ratio_vs_reference": 0.9632892185732532
|
| 907 |
+
}
|
| 908 |
+
}
|
| 909 |
+
],
|
| 910 |
+
"selected": {
|
| 911 |
+
"label": "affine8-head",
|
| 912 |
+
"precision": "affine8"
|
| 913 |
+
},
|
| 914 |
+
"precision_overrides": {
|
| 915 |
+
"language_model.lm_head": "affine8"
|
| 916 |
+
}
|
| 917 |
+
},
|
| 918 |
+
"precision_map": {
|
| 919 |
+
"vision_model": "bfloat16",
|
| 920 |
+
"projector": "mxfp8",
|
| 921 |
+
"language_model.model.embed_tokens": "mxfp8",
|
| 922 |
+
"language_model.model.norm": "bfloat16",
|
| 923 |
+
"language_model.lm_head": "affine8",
|
| 924 |
+
"language_model.model.layers.*.input_layernorm": "bfloat16",
|
| 925 |
+
"language_model.model.layers.*.post_attention_layernorm": "bfloat16",
|
| 926 |
+
"language_model.model.layers.*.self_attn.q_proj": "mxfp8",
|
| 927 |
+
"language_model.model.layers.*.self_attn.k_proj": "mxfp8",
|
| 928 |
+
"language_model.model.layers.*.self_attn.v_proj": "mxfp8",
|
| 929 |
+
"language_model.model.layers.*.self_attn.o_proj": "mxfp8",
|
| 930 |
+
"language_model.model.layers.*.mlp.gate_proj": "mxfp8",
|
| 931 |
+
"language_model.model.layers.*.mlp.up_proj": "mxfp8",
|
| 932 |
+
"language_model.model.layers.*.mlp.down_proj": "mxfp8",
|
| 933 |
+
"language_model.model.layers.*.mlp.shared_experts.gate_proj": "mxfp8",
|
| 934 |
+
"language_model.model.layers.*.mlp.shared_experts.up_proj": "mxfp8",
|
| 935 |
+
"language_model.model.layers.*.mlp.shared_experts.down_proj": "mxfp8",
|
| 936 |
+
"language_model.model.layers.*.mlp.switch_mlp.gate_proj": "mxfp8",
|
| 937 |
+
"language_model.model.layers.*.mlp.switch_mlp.up_proj": "mxfp8",
|
| 938 |
+
"language_model.model.layers.*.mlp.switch_mlp.down_proj": "mxfp8",
|
| 939 |
+
"language_model.model.layers.*.mlp.gate": "bfloat16",
|
| 940 |
+
"sam_model": "bfloat16",
|
| 941 |
+
"_generated_from": {
|
| 942 |
+
"source_model": "baidu/Unlimited-OCR",
|
| 943 |
+
"thresholds": {
|
| 944 |
+
"cer_delta": 0.02,
|
| 945 |
+
"digit_cer_delta": 0.02,
|
| 946 |
+
"table_score_degradation": 0.01
|
| 947 |
+
},
|
| 948 |
+
"decisions": {
|
| 949 |
+
"vision_encoder": {
|
| 950 |
+
"precision": "bfloat16",
|
| 951 |
+
"reasons": [
|
| 952 |
+
"cer_delta=2.423686",
|
| 953 |
+
"digit_cer_delta=1.358023",
|
| 954 |
+
"table_score_degradation=1.000000",
|
| 955 |
+
"protected OCR-critical group"
|
| 956 |
+
]
|
| 957 |
+
},
|
| 958 |
+
"vision_projector": {
|
| 959 |
+
"precision": "mxfp8",
|
| 960 |
+
"reasons": [
|
| 961 |
+
"all measured quality deltas were non-degrading"
|
| 962 |
+
]
|
| 963 |
+
},
|
| 964 |
+
"token_embeddings": {
|
| 965 |
+
"precision": "mxfp8",
|
| 966 |
+
"reasons": [
|
| 967 |
+
"all measured quality deltas were non-degrading"
|
| 968 |
+
]
|
| 969 |
+
},
|
| 970 |
+
"attention_q_proj": {
|
| 971 |
+
"precision": "base-map",
|
| 972 |
+
"reasons": [
|
| 973 |
+
"within thresholds"
|
| 974 |
+
]
|
| 975 |
+
},
|
| 976 |
+
"attention_k_proj": {
|
| 977 |
+
"precision": "base-map",
|
| 978 |
+
"reasons": [
|
| 979 |
+
"within thresholds"
|
| 980 |
+
]
|
| 981 |
+
},
|
| 982 |
+
"attention_v_proj": {
|
| 983 |
+
"precision": "base-map",
|
| 984 |
+
"reasons": [
|
| 985 |
+
"within thresholds"
|
| 986 |
+
]
|
| 987 |
+
},
|
| 988 |
+
"attention_o_proj": {
|
| 989 |
+
"precision": "base-map",
|
| 990 |
+
"reasons": [
|
| 991 |
+
"within thresholds"
|
| 992 |
+
]
|
| 993 |
+
},
|
| 994 |
+
"dense_mlp": {
|
| 995 |
+
"precision": "base-map",
|
| 996 |
+
"reasons": [
|
| 997 |
+
"within thresholds"
|
| 998 |
+
]
|
| 999 |
+
},
|
| 1000 |
+
"shared_experts": {
|
| 1001 |
+
"precision": "base-map",
|
| 1002 |
+
"reasons": [
|
| 1003 |
+
"within thresholds"
|
| 1004 |
+
]
|
| 1005 |
+
},
|
| 1006 |
+
"routed_experts": {
|
| 1007 |
+
"precision": "base-map",
|
| 1008 |
+
"reasons": [
|
| 1009 |
+
"within thresholds"
|
| 1010 |
+
]
|
| 1011 |
+
},
|
| 1012 |
+
"lm_head": {
|
| 1013 |
+
"precision": "affine8",
|
| 1014 |
+
"reasons": [
|
| 1015 |
+
"selected by joint quality/throughput calibration: affine8-head"
|
| 1016 |
+
]
|
| 1017 |
+
}
|
| 1018 |
+
},
|
| 1019 |
+
"calibration": {
|
| 1020 |
+
"selected": {
|
| 1021 |
+
"label": "affine8-head",
|
| 1022 |
+
"precision": "affine8"
|
| 1023 |
+
},
|
| 1024 |
+
"selection_policy": "fastest candidate passing existing quality and throughput limits"
|
| 1025 |
+
}
|
| 1026 |
+
}
|
| 1027 |
+
},
|
| 1028 |
+
"artifacts": {
|
| 1029 |
+
"bf16_accuracy": {
|
| 1030 |
+
"filename": "bf16_accuracy.json",
|
| 1031 |
+
"size": 4857,
|
| 1032 |
+
"sha256": "0de8511f1ba98f362d3ab712522115e534d7f129a8b587751ba5d5d8c95f272d"
|
| 1033 |
+
},
|
| 1034 |
+
"reference_accuracy": {
|
| 1035 |
+
"filename": "reference_accuracy.json",
|
| 1036 |
+
"size": 4923,
|
| 1037 |
+
"sha256": "354d3c17451bc9e1c6ec645d36ba2e133cd1b2393dae2d7825628832e11dc365"
|
| 1038 |
+
},
|
| 1039 |
+
"candidate_accuracy": {
|
| 1040 |
+
"filename": "candidate_accuracy.json",
|
| 1041 |
+
"size": 4863,
|
| 1042 |
+
"sha256": "20dbd578c93afa885a2b98627b5e8335b3c68ef14e84afa66bc9392f7e6c5daf"
|
| 1043 |
+
},
|
| 1044 |
+
"reference_performance": {
|
| 1045 |
+
"filename": "reference_performance.json",
|
| 1046 |
+
"size": 2211,
|
| 1047 |
+
"sha256": "74a5983677fcedab1cfe8193cc4138abfb011c6e9f2fa90d954cf3b52086888a"
|
| 1048 |
+
},
|
| 1049 |
+
"candidate_performance": {
|
| 1050 |
+
"filename": "candidate_performance.json",
|
| 1051 |
+
"size": 2219,
|
| 1052 |
+
"sha256": "52a7760cfcc1ce1f588fbce0642951874800fcb7952bed53644d81d91170862c"
|
| 1053 |
+
},
|
| 1054 |
+
"candidate_rswa": {
|
| 1055 |
+
"filename": "candidate_rswa.json",
|
| 1056 |
+
"size": 2374,
|
| 1057 |
+
"sha256": "612e0250c99eae03d4746f3687af57ab4ee43500fab376cd59d69be460ffa429"
|
| 1058 |
+
},
|
| 1059 |
+
"sensitivity_results": {
|
| 1060 |
+
"filename": "sensitivity_results.json",
|
| 1061 |
+
"size": 22710,
|
| 1062 |
+
"sha256": "63435bddcfe5d8e96921ed53eb1741ca731c9d362ab549391a436342b147d415"
|
| 1063 |
+
},
|
| 1064 |
+
"calibration_results": {
|
| 1065 |
+
"filename": "calibration_results.json",
|
| 1066 |
+
"size": 2542,
|
| 1067 |
+
"sha256": "416c1f8e332d69d9a753bea140e264a5573875f283c9eadc79b3ea61addc3d7b"
|
| 1068 |
+
},
|
| 1069 |
+
"generated_precision_map": {
|
| 1070 |
+
"filename": "generated_precision_map.json",
|
| 1071 |
+
"size": 3315,
|
| 1072 |
+
"sha256": "df9d9f6b2490d75e8589350b481fb1916a6dbf8a141c5f6958b0525d72fb1a46"
|
| 1073 |
+
},
|
| 1074 |
+
"provenance": {
|
| 1075 |
+
"filename": "provenance.json",
|
| 1076 |
+
"size": 5389,
|
| 1077 |
+
"sha256": "03c82c24826fdd6fdc0f5031ba67394979753584ff8949482a739dfef1719b47"
|
| 1078 |
+
}
|
| 1079 |
+
},
|
| 1080 |
+
"gates": [
|
| 1081 |
+
{
|
| 1082 |
+
"name": "weights_are_distinct",
|
| 1083 |
+
"passed": true,
|
| 1084 |
+
"actual": "a4f6e9934f90564906f42929542ad68493f0c6187ad431128e447debbf52b6f4",
|
| 1085 |
+
"limit": "different from 439c6acfe5e277537dfe3368b94a145a6b0da4c39e4bd00582977d030f292ad1",
|
| 1086 |
+
"detail": "Candidate aggregate digest must differ from the Sahil reference"
|
| 1087 |
+
},
|
| 1088 |
+
{
|
| 1089 |
+
"name": "weight_size_gb",
|
| 1090 |
+
"passed": true,
|
| 1091 |
+
"actual": 3.5792221864685416,
|
| 1092 |
+
"limit": 4.5,
|
| 1093 |
+
"detail": "Candidate Safetensors size"
|
| 1094 |
+
},
|
| 1095 |
+
{
|
| 1096 |
+
"name": "native_model_metadata",
|
| 1097 |
+
"passed": true,
|
| 1098 |
+
"actual": {
|
| 1099 |
+
"architecture": true,
|
| 1100 |
+
"model_type": true,
|
| 1101 |
+
"mxfp8": true,
|
| 1102 |
+
"sliding_window": true,
|
| 1103 |
+
"processor_class": true,
|
| 1104 |
+
"sft_format": true
|
| 1105 |
+
},
|
| 1106 |
+
"limit": true,
|
| 1107 |
+
"detail": "Native Unlimited-OCR, MXFP8, and R-SWA metadata"
|
| 1108 |
+
},
|
| 1109 |
+
{
|
| 1110 |
+
"name": "evaluation_coverage",
|
| 1111 |
+
"passed": true,
|
| 1112 |
+
"actual": {
|
| 1113 |
+
"samples": 12,
|
| 1114 |
+
"digit": 12,
|
| 1115 |
+
"cjk": 3,
|
| 1116 |
+
"table": 3
|
| 1117 |
+
},
|
| 1118 |
+
"limit": "all counts > 0",
|
| 1119 |
+
"detail": "Dataset must cover ordinary text, digits, CJK, and tables"
|
| 1120 |
+
},
|
| 1121 |
+
{
|
| 1122 |
+
"name": "same_evaluation_samples",
|
| 1123 |
+
"passed": true,
|
| 1124 |
+
"actual": [
|
| 1125 |
+
12,
|
| 1126 |
+
12,
|
| 1127 |
+
12
|
| 1128 |
+
],
|
| 1129 |
+
"limit": 12,
|
| 1130 |
+
"detail": "All three checkpoints must run the complete evaluation set"
|
| 1131 |
+
},
|
| 1132 |
+
{
|
| 1133 |
+
"name": "candidate_cer_vs_bf16",
|
| 1134 |
+
"passed": true,
|
| 1135 |
+
"actual": -0.0018033315701714847,
|
| 1136 |
+
"limit": 0.01,
|
| 1137 |
+
"detail": "Candidate minus BF16 absolute mean CER"
|
| 1138 |
+
},
|
| 1139 |
+
{
|
| 1140 |
+
"name": "candidate_cer_vs_reference",
|
| 1141 |
+
"passed": true,
|
| 1142 |
+
"actual": -0.8198643918447561,
|
| 1143 |
+
"limit": 0.005,
|
| 1144 |
+
"detail": "Candidate minus Sahil-reference absolute mean CER"
|
| 1145 |
+
},
|
| 1146 |
+
{
|
| 1147 |
+
"name": "candidate_digit_cer_vs_bf16",
|
| 1148 |
+
"passed": true,
|
| 1149 |
+
"actual": -0.0016074315849917697,
|
| 1150 |
+
"limit": 0.01,
|
| 1151 |
+
"detail": "Candidate minus BF16 digit CER"
|
| 1152 |
+
},
|
| 1153 |
+
{
|
| 1154 |
+
"name": "candidate_table_score_vs_bf16",
|
| 1155 |
+
"passed": true,
|
| 1156 |
+
"actual": 0.0,
|
| 1157 |
+
"limit": 0.01,
|
| 1158 |
+
"detail": "BF16 minus candidate mean table score"
|
| 1159 |
+
},
|
| 1160 |
+
{
|
| 1161 |
+
"name": "candidate_tps_vs_reference",
|
| 1162 |
+
"passed": true,
|
| 1163 |
+
"actual": 0.9503587809120344,
|
| 1164 |
+
"limit": 0.9,
|
| 1165 |
+
"detail": "Candidate decode throughput divided by Sahil-reference throughput"
|
| 1166 |
+
},
|
| 1167 |
+
{
|
| 1168 |
+
"name": "rswa_8k_bounded",
|
| 1169 |
+
"passed": true,
|
| 1170 |
+
"actual": {
|
| 1171 |
+
"cache_bounded": true,
|
| 1172 |
+
"tps_stable": true,
|
| 1173 |
+
"8k_test_passed": true
|
| 1174 |
+
},
|
| 1175 |
+
"limit": true,
|
| 1176 |
+
"detail": "8K generation, bounded cache, and stable throughput"
|
| 1177 |
+
}
|
| 1178 |
+
],
|
| 1179 |
+
"environment": {
|
| 1180 |
+
"platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O",
|
| 1181 |
+
"machine": "arm64",
|
| 1182 |
+
"python": "3.14.6",
|
| 1183 |
+
"versions": {
|
| 1184 |
+
"mlx": "0.32.0",
|
| 1185 |
+
"mlx-vlm": "0.6.6",
|
| 1186 |
+
"huggingface-hub": "1.24.0",
|
| 1187 |
+
"numpy": "2.5.1",
|
| 1188 |
+
"Pillow": "12.3.0"
|
| 1189 |
+
}
|
| 1190 |
+
}
|
| 1191 |
+
}
|
release/sensitivity_results.json
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"baseline": {
|
| 3 |
+
"num_samples": 12,
|
| 4 |
+
"num_digit_samples": 12,
|
| 5 |
+
"num_table_samples": 3,
|
| 6 |
+
"mean_cer": 0.2391877132924964,
|
| 7 |
+
"mean_digit_cer": 0.08733996757027829,
|
| 8 |
+
"mean_table_score": 1.0,
|
| 9 |
+
"total_inference_time": 37.96379516503657,
|
| 10 |
+
"avg_time_per_image": 3.163649597086381
|
| 11 |
+
},
|
| 12 |
+
"model_path": "baidu/Unlimited-OCR",
|
| 13 |
+
"eval_dir": "eval_dataset",
|
| 14 |
+
"prompt": "<image>document parsing.",
|
| 15 |
+
"max_tokens": 1024,
|
| 16 |
+
"groups_tested": [
|
| 17 |
+
"vision_encoder",
|
| 18 |
+
"vision_projector",
|
| 19 |
+
"token_embeddings",
|
| 20 |
+
"attention_q_proj",
|
| 21 |
+
"attention_k_proj",
|
| 22 |
+
"attention_v_proj",
|
| 23 |
+
"attention_o_proj",
|
| 24 |
+
"dense_mlp",
|
| 25 |
+
"shared_experts",
|
| 26 |
+
"routed_experts",
|
| 27 |
+
"lm_head"
|
| 28 |
+
],
|
| 29 |
+
"layer_groups": {
|
| 30 |
+
"vision_encoder": {
|
| 31 |
+
"status": "success",
|
| 32 |
+
"matched_module_count": 145,
|
| 33 |
+
"matched_modules": [
|
| 34 |
+
"sam_model.blocks.0.attn.proj",
|
| 35 |
+
"sam_model.blocks.0.attn.qkv",
|
| 36 |
+
"sam_model.blocks.0.mlp.lin1",
|
| 37 |
+
"sam_model.blocks.0.mlp.lin2",
|
| 38 |
+
"sam_model.blocks.1.attn.proj",
|
| 39 |
+
"sam_model.blocks.1.attn.qkv",
|
| 40 |
+
"sam_model.blocks.1.mlp.lin1",
|
| 41 |
+
"sam_model.blocks.1.mlp.lin2",
|
| 42 |
+
"sam_model.blocks.10.attn.proj",
|
| 43 |
+
"sam_model.blocks.10.attn.qkv",
|
| 44 |
+
"sam_model.blocks.10.mlp.lin1",
|
| 45 |
+
"sam_model.blocks.10.mlp.lin2",
|
| 46 |
+
"sam_model.blocks.11.attn.proj",
|
| 47 |
+
"sam_model.blocks.11.attn.qkv",
|
| 48 |
+
"sam_model.blocks.11.mlp.lin1",
|
| 49 |
+
"sam_model.blocks.11.mlp.lin2",
|
| 50 |
+
"sam_model.blocks.2.attn.proj",
|
| 51 |
+
"sam_model.blocks.2.attn.qkv",
|
| 52 |
+
"sam_model.blocks.2.mlp.lin1",
|
| 53 |
+
"sam_model.blocks.2.mlp.lin2",
|
| 54 |
+
"sam_model.blocks.3.attn.proj",
|
| 55 |
+
"sam_model.blocks.3.attn.qkv",
|
| 56 |
+
"sam_model.blocks.3.mlp.lin1",
|
| 57 |
+
"sam_model.blocks.3.mlp.lin2",
|
| 58 |
+
"sam_model.blocks.4.attn.proj",
|
| 59 |
+
"sam_model.blocks.4.attn.qkv",
|
| 60 |
+
"sam_model.blocks.4.mlp.lin1",
|
| 61 |
+
"sam_model.blocks.4.mlp.lin2",
|
| 62 |
+
"sam_model.blocks.5.attn.proj",
|
| 63 |
+
"sam_model.blocks.5.attn.qkv",
|
| 64 |
+
"sam_model.blocks.5.mlp.lin1",
|
| 65 |
+
"sam_model.blocks.5.mlp.lin2",
|
| 66 |
+
"sam_model.blocks.6.attn.proj",
|
| 67 |
+
"sam_model.blocks.6.attn.qkv",
|
| 68 |
+
"sam_model.blocks.6.mlp.lin1",
|
| 69 |
+
"sam_model.blocks.6.mlp.lin2",
|
| 70 |
+
"sam_model.blocks.7.attn.proj",
|
| 71 |
+
"sam_model.blocks.7.attn.qkv",
|
| 72 |
+
"sam_model.blocks.7.mlp.lin1",
|
| 73 |
+
"sam_model.blocks.7.mlp.lin2",
|
| 74 |
+
"sam_model.blocks.8.attn.proj",
|
| 75 |
+
"sam_model.blocks.8.attn.qkv",
|
| 76 |
+
"sam_model.blocks.8.mlp.lin1",
|
| 77 |
+
"sam_model.blocks.8.mlp.lin2",
|
| 78 |
+
"sam_model.blocks.9.attn.proj",
|
| 79 |
+
"sam_model.blocks.9.attn.qkv",
|
| 80 |
+
"sam_model.blocks.9.mlp.lin1",
|
| 81 |
+
"sam_model.blocks.9.mlp.lin2",
|
| 82 |
+
"vision_model.embeddings.position_embedding",
|
| 83 |
+
"vision_model.transformer.layers.0.mlp.fc1",
|
| 84 |
+
"vision_model.transformer.layers.0.mlp.fc2",
|
| 85 |
+
"vision_model.transformer.layers.0.self_attn.out_proj",
|
| 86 |
+
"vision_model.transformer.layers.0.self_attn.qkv_proj",
|
| 87 |
+
"vision_model.transformer.layers.1.mlp.fc1",
|
| 88 |
+
"vision_model.transformer.layers.1.mlp.fc2",
|
| 89 |
+
"vision_model.transformer.layers.1.self_attn.out_proj",
|
| 90 |
+
"vision_model.transformer.layers.1.self_attn.qkv_proj",
|
| 91 |
+
"vision_model.transformer.layers.10.mlp.fc1",
|
| 92 |
+
"vision_model.transformer.layers.10.mlp.fc2",
|
| 93 |
+
"vision_model.transformer.layers.10.self_attn.out_proj",
|
| 94 |
+
"vision_model.transformer.layers.10.self_attn.qkv_proj",
|
| 95 |
+
"vision_model.transformer.layers.11.mlp.fc1",
|
| 96 |
+
"vision_model.transformer.layers.11.mlp.fc2",
|
| 97 |
+
"vision_model.transformer.layers.11.self_attn.out_proj",
|
| 98 |
+
"vision_model.transformer.layers.11.self_attn.qkv_proj",
|
| 99 |
+
"vision_model.transformer.layers.12.mlp.fc1",
|
| 100 |
+
"vision_model.transformer.layers.12.mlp.fc2",
|
| 101 |
+
"vision_model.transformer.layers.12.self_attn.out_proj",
|
| 102 |
+
"vision_model.transformer.layers.12.self_attn.qkv_proj",
|
| 103 |
+
"vision_model.transformer.layers.13.mlp.fc1",
|
| 104 |
+
"vision_model.transformer.layers.13.mlp.fc2",
|
| 105 |
+
"vision_model.transformer.layers.13.self_attn.out_proj",
|
| 106 |
+
"vision_model.transformer.layers.13.self_attn.qkv_proj",
|
| 107 |
+
"vision_model.transformer.layers.14.mlp.fc1",
|
| 108 |
+
"vision_model.transformer.layers.14.mlp.fc2",
|
| 109 |
+
"vision_model.transformer.layers.14.self_attn.out_proj",
|
| 110 |
+
"vision_model.transformer.layers.14.self_attn.qkv_proj",
|
| 111 |
+
"vision_model.transformer.layers.15.mlp.fc1",
|
| 112 |
+
"vision_model.transformer.layers.15.mlp.fc2",
|
| 113 |
+
"vision_model.transformer.layers.15.self_attn.out_proj",
|
| 114 |
+
"vision_model.transformer.layers.15.self_attn.qkv_proj",
|
| 115 |
+
"vision_model.transformer.layers.16.mlp.fc1",
|
| 116 |
+
"vision_model.transformer.layers.16.mlp.fc2",
|
| 117 |
+
"vision_model.transformer.layers.16.self_attn.out_proj",
|
| 118 |
+
"vision_model.transformer.layers.16.self_attn.qkv_proj",
|
| 119 |
+
"vision_model.transformer.layers.17.mlp.fc1",
|
| 120 |
+
"vision_model.transformer.layers.17.mlp.fc2",
|
| 121 |
+
"vision_model.transformer.layers.17.self_attn.out_proj",
|
| 122 |
+
"vision_model.transformer.layers.17.self_attn.qkv_proj",
|
| 123 |
+
"vision_model.transformer.layers.18.mlp.fc1",
|
| 124 |
+
"vision_model.transformer.layers.18.mlp.fc2",
|
| 125 |
+
"vision_model.transformer.layers.18.self_attn.out_proj",
|
| 126 |
+
"vision_model.transformer.layers.18.self_attn.qkv_proj",
|
| 127 |
+
"vision_model.transformer.layers.19.mlp.fc1",
|
| 128 |
+
"vision_model.transformer.layers.19.mlp.fc2",
|
| 129 |
+
"vision_model.transformer.layers.19.self_attn.out_proj",
|
| 130 |
+
"vision_model.transformer.layers.19.self_attn.qkv_proj",
|
| 131 |
+
"vision_model.transformer.layers.2.mlp.fc1",
|
| 132 |
+
"vision_model.transformer.layers.2.mlp.fc2",
|
| 133 |
+
"vision_model.transformer.layers.2.self_attn.out_proj",
|
| 134 |
+
"vision_model.transformer.layers.2.self_attn.qkv_proj",
|
| 135 |
+
"vision_model.transformer.layers.20.mlp.fc1",
|
| 136 |
+
"vision_model.transformer.layers.20.mlp.fc2",
|
| 137 |
+
"vision_model.transformer.layers.20.self_attn.out_proj",
|
| 138 |
+
"vision_model.transformer.layers.20.self_attn.qkv_proj",
|
| 139 |
+
"vision_model.transformer.layers.21.mlp.fc1",
|
| 140 |
+
"vision_model.transformer.layers.21.mlp.fc2",
|
| 141 |
+
"vision_model.transformer.layers.21.self_attn.out_proj",
|
| 142 |
+
"vision_model.transformer.layers.21.self_attn.qkv_proj",
|
| 143 |
+
"vision_model.transformer.layers.22.mlp.fc1",
|
| 144 |
+
"vision_model.transformer.layers.22.mlp.fc2",
|
| 145 |
+
"vision_model.transformer.layers.22.self_attn.out_proj",
|
| 146 |
+
"vision_model.transformer.layers.22.self_attn.qkv_proj",
|
| 147 |
+
"vision_model.transformer.layers.23.mlp.fc1",
|
| 148 |
+
"vision_model.transformer.layers.23.mlp.fc2",
|
| 149 |
+
"vision_model.transformer.layers.23.self_attn.out_proj",
|
| 150 |
+
"vision_model.transformer.layers.23.self_attn.qkv_proj",
|
| 151 |
+
"vision_model.transformer.layers.3.mlp.fc1",
|
| 152 |
+
"vision_model.transformer.layers.3.mlp.fc2",
|
| 153 |
+
"vision_model.transformer.layers.3.self_attn.out_proj",
|
| 154 |
+
"vision_model.transformer.layers.3.self_attn.qkv_proj",
|
| 155 |
+
"vision_model.transformer.layers.4.mlp.fc1",
|
| 156 |
+
"vision_model.transformer.layers.4.mlp.fc2",
|
| 157 |
+
"vision_model.transformer.layers.4.self_attn.out_proj",
|
| 158 |
+
"vision_model.transformer.layers.4.self_attn.qkv_proj",
|
| 159 |
+
"vision_model.transformer.layers.5.mlp.fc1",
|
| 160 |
+
"vision_model.transformer.layers.5.mlp.fc2",
|
| 161 |
+
"vision_model.transformer.layers.5.self_attn.out_proj",
|
| 162 |
+
"vision_model.transformer.layers.5.self_attn.qkv_proj",
|
| 163 |
+
"vision_model.transformer.layers.6.mlp.fc1",
|
| 164 |
+
"vision_model.transformer.layers.6.mlp.fc2",
|
| 165 |
+
"vision_model.transformer.layers.6.self_attn.out_proj",
|
| 166 |
+
"vision_model.transformer.layers.6.self_attn.qkv_proj",
|
| 167 |
+
"vision_model.transformer.layers.7.mlp.fc1",
|
| 168 |
+
"vision_model.transformer.layers.7.mlp.fc2",
|
| 169 |
+
"vision_model.transformer.layers.7.self_attn.out_proj",
|
| 170 |
+
"vision_model.transformer.layers.7.self_attn.qkv_proj",
|
| 171 |
+
"vision_model.transformer.layers.8.mlp.fc1",
|
| 172 |
+
"vision_model.transformer.layers.8.mlp.fc2",
|
| 173 |
+
"vision_model.transformer.layers.8.self_attn.out_proj",
|
| 174 |
+
"vision_model.transformer.layers.8.self_attn.qkv_proj",
|
| 175 |
+
"vision_model.transformer.layers.9.mlp.fc1",
|
| 176 |
+
"vision_model.transformer.layers.9.mlp.fc2",
|
| 177 |
+
"vision_model.transformer.layers.9.self_attn.out_proj",
|
| 178 |
+
"vision_model.transformer.layers.9.self_attn.qkv_proj"
|
| 179 |
+
],
|
| 180 |
+
"metrics": {
|
| 181 |
+
"num_samples": 12,
|
| 182 |
+
"num_digit_samples": 12,
|
| 183 |
+
"num_table_samples": 3,
|
| 184 |
+
"mean_cer": 2.6628739674476654,
|
| 185 |
+
"mean_digit_cer": 1.445363129900987,
|
| 186 |
+
"mean_table_score": 0.0,
|
| 187 |
+
"total_inference_time": 82.4734385000047,
|
| 188 |
+
"avg_time_per_image": 6.872786541667058
|
| 189 |
+
},
|
| 190 |
+
"cer_delta": 2.423686254155169,
|
| 191 |
+
"digit_cer_delta": 1.3580231623307086,
|
| 192 |
+
"table_score_degradation": 1.0,
|
| 193 |
+
"sensitivity_rank": 1
|
| 194 |
+
},
|
| 195 |
+
"vision_projector": {
|
| 196 |
+
"status": "success",
|
| 197 |
+
"matched_module_count": 1,
|
| 198 |
+
"matched_modules": [
|
| 199 |
+
"projector.layers"
|
| 200 |
+
],
|
| 201 |
+
"metrics": {
|
| 202 |
+
"num_samples": 12,
|
| 203 |
+
"num_digit_samples": 12,
|
| 204 |
+
"num_table_samples": 3,
|
| 205 |
+
"mean_cer": 0.23591319611894124,
|
| 206 |
+
"mean_digit_cer": 0.08425354781719185,
|
| 207 |
+
"mean_table_score": 1.0,
|
| 208 |
+
"total_inference_time": 37.98282545800612,
|
| 209 |
+
"avg_time_per_image": 3.165235454833843
|
| 210 |
+
},
|
| 211 |
+
"cer_delta": -0.003274517173555175,
|
| 212 |
+
"digit_cer_delta": -0.0030864197530864335,
|
| 213 |
+
"table_score_degradation": 0.0,
|
| 214 |
+
"sensitivity_rank": 7
|
| 215 |
+
},
|
| 216 |
+
"token_embeddings": {
|
| 217 |
+
"status": "success",
|
| 218 |
+
"matched_module_count": 1,
|
| 219 |
+
"matched_modules": [
|
| 220 |
+
"language_model.model.embed_tokens"
|
| 221 |
+
],
|
| 222 |
+
"metrics": {
|
| 223 |
+
"num_samples": 12,
|
| 224 |
+
"num_digit_samples": 12,
|
| 225 |
+
"num_table_samples": 3,
|
| 226 |
+
"mean_cer": 0.2326891302977425,
|
| 227 |
+
"mean_digit_cer": 0.08249916185227958,
|
| 228 |
+
"mean_table_score": 1.0,
|
| 229 |
+
"total_inference_time": 38.66650962494896,
|
| 230 |
+
"avg_time_per_image": 3.222209135412413
|
| 231 |
+
},
|
| 232 |
+
"cer_delta": -0.006498582994753921,
|
| 233 |
+
"digit_cer_delta": -0.004840805717998706,
|
| 234 |
+
"table_score_degradation": 0.0,
|
| 235 |
+
"sensitivity_rank": 10
|
| 236 |
+
},
|
| 237 |
+
"attention_q_proj": {
|
| 238 |
+
"status": "success",
|
| 239 |
+
"matched_module_count": 12,
|
| 240 |
+
"matched_modules": [
|
| 241 |
+
"language_model.model.layers.0.self_attn.q_proj",
|
| 242 |
+
"language_model.model.layers.1.self_attn.q_proj",
|
| 243 |
+
"language_model.model.layers.10.self_attn.q_proj",
|
| 244 |
+
"language_model.model.layers.11.self_attn.q_proj",
|
| 245 |
+
"language_model.model.layers.2.self_attn.q_proj",
|
| 246 |
+
"language_model.model.layers.3.self_attn.q_proj",
|
| 247 |
+
"language_model.model.layers.4.self_attn.q_proj",
|
| 248 |
+
"language_model.model.layers.5.self_attn.q_proj",
|
| 249 |
+
"language_model.model.layers.6.self_attn.q_proj",
|
| 250 |
+
"language_model.model.layers.7.self_attn.q_proj",
|
| 251 |
+
"language_model.model.layers.8.self_attn.q_proj",
|
| 252 |
+
"language_model.model.layers.9.self_attn.q_proj"
|
| 253 |
+
],
|
| 254 |
+
"metrics": {
|
| 255 |
+
"num_samples": 12,
|
| 256 |
+
"num_digit_samples": 12,
|
| 257 |
+
"num_table_samples": 3,
|
| 258 |
+
"mean_cer": 0.21507560490000377,
|
| 259 |
+
"mean_digit_cer": 0.09653424957157782,
|
| 260 |
+
"mean_table_score": 1.0,
|
| 261 |
+
"total_inference_time": 38.38538125100604,
|
| 262 |
+
"avg_time_per_image": 3.19878177091717
|
| 263 |
+
},
|
| 264 |
+
"cer_delta": -0.024112108392492643,
|
| 265 |
+
"digit_cer_delta": 0.009194282001299528,
|
| 266 |
+
"table_score_degradation": 0.0,
|
| 267 |
+
"sensitivity_rank": 11
|
| 268 |
+
},
|
| 269 |
+
"attention_k_proj": {
|
| 270 |
+
"status": "success",
|
| 271 |
+
"matched_module_count": 12,
|
| 272 |
+
"matched_modules": [
|
| 273 |
+
"language_model.model.layers.0.self_attn.k_proj",
|
| 274 |
+
"language_model.model.layers.1.self_attn.k_proj",
|
| 275 |
+
"language_model.model.layers.10.self_attn.k_proj",
|
| 276 |
+
"language_model.model.layers.11.self_attn.k_proj",
|
| 277 |
+
"language_model.model.layers.2.self_attn.k_proj",
|
| 278 |
+
"language_model.model.layers.3.self_attn.k_proj",
|
| 279 |
+
"language_model.model.layers.4.self_attn.k_proj",
|
| 280 |
+
"language_model.model.layers.5.self_attn.k_proj",
|
| 281 |
+
"language_model.model.layers.6.self_attn.k_proj",
|
| 282 |
+
"language_model.model.layers.7.self_attn.k_proj",
|
| 283 |
+
"language_model.model.layers.8.self_attn.k_proj",
|
| 284 |
+
"language_model.model.layers.9.self_attn.k_proj"
|
| 285 |
+
],
|
| 286 |
+
"metrics": {
|
| 287 |
+
"num_samples": 12,
|
| 288 |
+
"num_digit_samples": 12,
|
| 289 |
+
"num_table_samples": 3,
|
| 290 |
+
"mean_cer": 0.23424214947533464,
|
| 291 |
+
"mean_digit_cer": 0.08425354781719185,
|
| 292 |
+
"mean_table_score": 1.0,
|
| 293 |
+
"total_inference_time": 38.175402873021085,
|
| 294 |
+
"avg_time_per_image": 3.181283572751757
|
| 295 |
+
},
|
| 296 |
+
"cer_delta": -0.004945563817161769,
|
| 297 |
+
"digit_cer_delta": -0.0030864197530864335,
|
| 298 |
+
"table_score_degradation": 0.0,
|
| 299 |
+
"sensitivity_rank": 9
|
| 300 |
+
},
|
| 301 |
+
"attention_v_proj": {
|
| 302 |
+
"status": "success",
|
| 303 |
+
"matched_module_count": 12,
|
| 304 |
+
"matched_modules": [
|
| 305 |
+
"language_model.model.layers.0.self_attn.v_proj",
|
| 306 |
+
"language_model.model.layers.1.self_attn.v_proj",
|
| 307 |
+
"language_model.model.layers.10.self_attn.v_proj",
|
| 308 |
+
"language_model.model.layers.11.self_attn.v_proj",
|
| 309 |
+
"language_model.model.layers.2.self_attn.v_proj",
|
| 310 |
+
"language_model.model.layers.3.self_attn.v_proj",
|
| 311 |
+
"language_model.model.layers.4.self_attn.v_proj",
|
| 312 |
+
"language_model.model.layers.5.self_attn.v_proj",
|
| 313 |
+
"language_model.model.layers.6.self_attn.v_proj",
|
| 314 |
+
"language_model.model.layers.7.self_attn.v_proj",
|
| 315 |
+
"language_model.model.layers.8.self_attn.v_proj",
|
| 316 |
+
"language_model.model.layers.9.self_attn.v_proj"
|
| 317 |
+
],
|
| 318 |
+
"metrics": {
|
| 319 |
+
"num_samples": 12,
|
| 320 |
+
"num_digit_samples": 12,
|
| 321 |
+
"num_table_samples": 3,
|
| 322 |
+
"mean_cer": 0.23642534441989824,
|
| 323 |
+
"mean_digit_cer": 0.08425354781719185,
|
| 324 |
+
"mean_table_score": 1.0,
|
| 325 |
+
"total_inference_time": 41.72621229101787,
|
| 326 |
+
"avg_time_per_image": 3.477184357584823
|
| 327 |
+
},
|
| 328 |
+
"cer_delta": -0.0027623688725981754,
|
| 329 |
+
"digit_cer_delta": -0.0030864197530864335,
|
| 330 |
+
"table_score_degradation": 0.0,
|
| 331 |
+
"sensitivity_rank": 5
|
| 332 |
+
},
|
| 333 |
+
"attention_o_proj": {
|
| 334 |
+
"status": "success",
|
| 335 |
+
"matched_module_count": 12,
|
| 336 |
+
"matched_modules": [
|
| 337 |
+
"language_model.model.layers.0.self_attn.o_proj",
|
| 338 |
+
"language_model.model.layers.1.self_attn.o_proj",
|
| 339 |
+
"language_model.model.layers.10.self_attn.o_proj",
|
| 340 |
+
"language_model.model.layers.11.self_attn.o_proj",
|
| 341 |
+
"language_model.model.layers.2.self_attn.o_proj",
|
| 342 |
+
"language_model.model.layers.3.self_attn.o_proj",
|
| 343 |
+
"language_model.model.layers.4.self_attn.o_proj",
|
| 344 |
+
"language_model.model.layers.5.self_attn.o_proj",
|
| 345 |
+
"language_model.model.layers.6.self_attn.o_proj",
|
| 346 |
+
"language_model.model.layers.7.self_attn.o_proj",
|
| 347 |
+
"language_model.model.layers.8.self_attn.o_proj",
|
| 348 |
+
"language_model.model.layers.9.self_attn.o_proj"
|
| 349 |
+
],
|
| 350 |
+
"metrics": {
|
| 351 |
+
"num_samples": 12,
|
| 352 |
+
"num_digit_samples": 12,
|
| 353 |
+
"num_table_samples": 3,
|
| 354 |
+
"mean_cer": 0.2359826017166606,
|
| 355 |
+
"mean_digit_cer": 0.07714420469193685,
|
| 356 |
+
"mean_table_score": 1.0,
|
| 357 |
+
"total_inference_time": 41.9442965429771,
|
| 358 |
+
"avg_time_per_image": 3.4953580452480915
|
| 359 |
+
},
|
| 360 |
+
"cer_delta": -0.003205111575835806,
|
| 361 |
+
"digit_cer_delta": -0.010195762878341438,
|
| 362 |
+
"table_score_degradation": 0.0,
|
| 363 |
+
"sensitivity_rank": 6
|
| 364 |
+
},
|
| 365 |
+
"dense_mlp": {
|
| 366 |
+
"status": "success",
|
| 367 |
+
"matched_module_count": 3,
|
| 368 |
+
"matched_modules": [
|
| 369 |
+
"language_model.model.layers.0.mlp.down_proj",
|
| 370 |
+
"language_model.model.layers.0.mlp.gate_proj",
|
| 371 |
+
"language_model.model.layers.0.mlp.up_proj"
|
| 372 |
+
],
|
| 373 |
+
"metrics": {
|
| 374 |
+
"num_samples": 12,
|
| 375 |
+
"num_digit_samples": 12,
|
| 376 |
+
"num_table_samples": 3,
|
| 377 |
+
"mean_cer": 0.23568756434998817,
|
| 378 |
+
"mean_digit_cer": 0.08425354781719185,
|
| 379 |
+
"mean_table_score": 1.0,
|
| 380 |
+
"total_inference_time": 41.424109207015135,
|
| 381 |
+
"avg_time_per_image": 3.4520091005845948
|
| 382 |
+
},
|
| 383 |
+
"cer_delta": -0.0035001489425082466,
|
| 384 |
+
"digit_cer_delta": -0.0030864197530864335,
|
| 385 |
+
"table_score_degradation": 0.0,
|
| 386 |
+
"sensitivity_rank": 8
|
| 387 |
+
},
|
| 388 |
+
"shared_experts": {
|
| 389 |
+
"status": "success",
|
| 390 |
+
"matched_module_count": 33,
|
| 391 |
+
"matched_modules": [
|
| 392 |
+
"language_model.model.layers.1.mlp.shared_experts.down_proj",
|
| 393 |
+
"language_model.model.layers.1.mlp.shared_experts.gate_proj",
|
| 394 |
+
"language_model.model.layers.1.mlp.shared_experts.up_proj",
|
| 395 |
+
"language_model.model.layers.10.mlp.shared_experts.down_proj",
|
| 396 |
+
"language_model.model.layers.10.mlp.shared_experts.gate_proj",
|
| 397 |
+
"language_model.model.layers.10.mlp.shared_experts.up_proj",
|
| 398 |
+
"language_model.model.layers.11.mlp.shared_experts.down_proj",
|
| 399 |
+
"language_model.model.layers.11.mlp.shared_experts.gate_proj",
|
| 400 |
+
"language_model.model.layers.11.mlp.shared_experts.up_proj",
|
| 401 |
+
"language_model.model.layers.2.mlp.shared_experts.down_proj",
|
| 402 |
+
"language_model.model.layers.2.mlp.shared_experts.gate_proj",
|
| 403 |
+
"language_model.model.layers.2.mlp.shared_experts.up_proj",
|
| 404 |
+
"language_model.model.layers.3.mlp.shared_experts.down_proj",
|
| 405 |
+
"language_model.model.layers.3.mlp.shared_experts.gate_proj",
|
| 406 |
+
"language_model.model.layers.3.mlp.shared_experts.up_proj",
|
| 407 |
+
"language_model.model.layers.4.mlp.shared_experts.down_proj",
|
| 408 |
+
"language_model.model.layers.4.mlp.shared_experts.gate_proj",
|
| 409 |
+
"language_model.model.layers.4.mlp.shared_experts.up_proj",
|
| 410 |
+
"language_model.model.layers.5.mlp.shared_experts.down_proj",
|
| 411 |
+
"language_model.model.layers.5.mlp.shared_experts.gate_proj",
|
| 412 |
+
"language_model.model.layers.5.mlp.shared_experts.up_proj",
|
| 413 |
+
"language_model.model.layers.6.mlp.shared_experts.down_proj",
|
| 414 |
+
"language_model.model.layers.6.mlp.shared_experts.gate_proj",
|
| 415 |
+
"language_model.model.layers.6.mlp.shared_experts.up_proj",
|
| 416 |
+
"language_model.model.layers.7.mlp.shared_experts.down_proj",
|
| 417 |
+
"language_model.model.layers.7.mlp.shared_experts.gate_proj",
|
| 418 |
+
"language_model.model.layers.7.mlp.shared_experts.up_proj",
|
| 419 |
+
"language_model.model.layers.8.mlp.shared_experts.down_proj",
|
| 420 |
+
"language_model.model.layers.8.mlp.shared_experts.gate_proj",
|
| 421 |
+
"language_model.model.layers.8.mlp.shared_experts.up_proj",
|
| 422 |
+
"language_model.model.layers.9.mlp.shared_experts.down_proj",
|
| 423 |
+
"language_model.model.layers.9.mlp.shared_experts.gate_proj",
|
| 424 |
+
"language_model.model.layers.9.mlp.shared_experts.up_proj"
|
| 425 |
+
],
|
| 426 |
+
"metrics": {
|
| 427 |
+
"num_samples": 12,
|
| 428 |
+
"num_digit_samples": 12,
|
| 429 |
+
"num_table_samples": 3,
|
| 430 |
+
"mean_cer": 0.23649792801080774,
|
| 431 |
+
"mean_digit_cer": 0.09592829886362793,
|
| 432 |
+
"mean_table_score": 1.0,
|
| 433 |
+
"total_inference_time": 37.84185174998129,
|
| 434 |
+
"avg_time_per_image": 3.153487645831774
|
| 435 |
+
},
|
| 436 |
+
"cer_delta": -0.0026897852816886725,
|
| 437 |
+
"digit_cer_delta": 0.00858833129334964,
|
| 438 |
+
"table_score_degradation": 0.0,
|
| 439 |
+
"sensitivity_rank": 4
|
| 440 |
+
},
|
| 441 |
+
"routed_experts": {
|
| 442 |
+
"status": "success",
|
| 443 |
+
"matched_module_count": 33,
|
| 444 |
+
"matched_modules": [
|
| 445 |
+
"language_model.model.layers.1.mlp.switch_mlp.down_proj",
|
| 446 |
+
"language_model.model.layers.1.mlp.switch_mlp.gate_proj",
|
| 447 |
+
"language_model.model.layers.1.mlp.switch_mlp.up_proj",
|
| 448 |
+
"language_model.model.layers.10.mlp.switch_mlp.down_proj",
|
| 449 |
+
"language_model.model.layers.10.mlp.switch_mlp.gate_proj",
|
| 450 |
+
"language_model.model.layers.10.mlp.switch_mlp.up_proj",
|
| 451 |
+
"language_model.model.layers.11.mlp.switch_mlp.down_proj",
|
| 452 |
+
"language_model.model.layers.11.mlp.switch_mlp.gate_proj",
|
| 453 |
+
"language_model.model.layers.11.mlp.switch_mlp.up_proj",
|
| 454 |
+
"language_model.model.layers.2.mlp.switch_mlp.down_proj",
|
| 455 |
+
"language_model.model.layers.2.mlp.switch_mlp.gate_proj",
|
| 456 |
+
"language_model.model.layers.2.mlp.switch_mlp.up_proj",
|
| 457 |
+
"language_model.model.layers.3.mlp.switch_mlp.down_proj",
|
| 458 |
+
"language_model.model.layers.3.mlp.switch_mlp.gate_proj",
|
| 459 |
+
"language_model.model.layers.3.mlp.switch_mlp.up_proj",
|
| 460 |
+
"language_model.model.layers.4.mlp.switch_mlp.down_proj",
|
| 461 |
+
"language_model.model.layers.4.mlp.switch_mlp.gate_proj",
|
| 462 |
+
"language_model.model.layers.4.mlp.switch_mlp.up_proj",
|
| 463 |
+
"language_model.model.layers.5.mlp.switch_mlp.down_proj",
|
| 464 |
+
"language_model.model.layers.5.mlp.switch_mlp.gate_proj",
|
| 465 |
+
"language_model.model.layers.5.mlp.switch_mlp.up_proj",
|
| 466 |
+
"language_model.model.layers.6.mlp.switch_mlp.down_proj",
|
| 467 |
+
"language_model.model.layers.6.mlp.switch_mlp.gate_proj",
|
| 468 |
+
"language_model.model.layers.6.mlp.switch_mlp.up_proj",
|
| 469 |
+
"language_model.model.layers.7.mlp.switch_mlp.down_proj",
|
| 470 |
+
"language_model.model.layers.7.mlp.switch_mlp.gate_proj",
|
| 471 |
+
"language_model.model.layers.7.mlp.switch_mlp.up_proj",
|
| 472 |
+
"language_model.model.layers.8.mlp.switch_mlp.down_proj",
|
| 473 |
+
"language_model.model.layers.8.mlp.switch_mlp.gate_proj",
|
| 474 |
+
"language_model.model.layers.8.mlp.switch_mlp.up_proj",
|
| 475 |
+
"language_model.model.layers.9.mlp.switch_mlp.down_proj",
|
| 476 |
+
"language_model.model.layers.9.mlp.switch_mlp.gate_proj",
|
| 477 |
+
"language_model.model.layers.9.mlp.switch_mlp.up_proj"
|
| 478 |
+
],
|
| 479 |
+
"metrics": {
|
| 480 |
+
"num_samples": 12,
|
| 481 |
+
"num_digit_samples": 12,
|
| 482 |
+
"num_table_samples": 3,
|
| 483 |
+
"mean_cer": 0.23911822127011442,
|
| 484 |
+
"mean_digit_cer": 0.08452479009169811,
|
| 485 |
+
"mean_table_score": 1.0,
|
| 486 |
+
"total_inference_time": 36.668351295011234,
|
| 487 |
+
"avg_time_per_image": 3.0556959412509364
|
| 488 |
+
},
|
| 489 |
+
"cer_delta": -6.949202238198948e-05,
|
| 490 |
+
"digit_cer_delta": -0.0028151774785801775,
|
| 491 |
+
"table_score_degradation": 0.0,
|
| 492 |
+
"sensitivity_rank": 3
|
| 493 |
+
},
|
| 494 |
+
"lm_head": {
|
| 495 |
+
"status": "success",
|
| 496 |
+
"matched_module_count": 1,
|
| 497 |
+
"matched_modules": [
|
| 498 |
+
"language_model.lm_head"
|
| 499 |
+
],
|
| 500 |
+
"metrics": {
|
| 501 |
+
"num_samples": 12,
|
| 502 |
+
"num_digit_samples": 12,
|
| 503 |
+
"num_table_samples": 3,
|
| 504 |
+
"mean_cer": 0.25282999056583605,
|
| 505 |
+
"mean_digit_cer": 0.12301897991595728,
|
| 506 |
+
"mean_table_score": 1.0,
|
| 507 |
+
"total_inference_time": 37.152046875009546,
|
| 508 |
+
"avg_time_per_image": 3.0960039062507954
|
| 509 |
+
},
|
| 510 |
+
"cer_delta": 0.013642277273339637,
|
| 511 |
+
"digit_cer_delta": 0.035679012345678995,
|
| 512 |
+
"table_score_degradation": 0.0,
|
| 513 |
+
"sensitivity_rank": 2
|
| 514 |
+
}
|
| 515 |
+
}
|
| 516 |
+
}
|
special_tokens_map.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"additional_special_tokens": [
|
| 3 |
+
{
|
| 4 |
+
"content": "<|User|>",
|
| 5 |
+
"lstrip": false,
|
| 6 |
+
"normalized": false,
|
| 7 |
+
"rstrip": false,
|
| 8 |
+
"single_word": false
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
"content": "<|Assistant|>",
|
| 12 |
+
"lstrip": false,
|
| 13 |
+
"normalized": false,
|
| 14 |
+
"rstrip": false,
|
| 15 |
+
"single_word": false
|
| 16 |
+
}
|
| 17 |
+
],
|
| 18 |
+
"bos_token": {
|
| 19 |
+
"content": "<|begin▁of▁sentence|>",
|
| 20 |
+
"lstrip": false,
|
| 21 |
+
"normalized": false,
|
| 22 |
+
"rstrip": false,
|
| 23 |
+
"single_word": false
|
| 24 |
+
},
|
| 25 |
+
"eos_token": {
|
| 26 |
+
"content": "<|end▁of▁sentence|>",
|
| 27 |
+
"lstrip": false,
|
| 28 |
+
"normalized": false,
|
| 29 |
+
"rstrip": false,
|
| 30 |
+
"single_word": false
|
| 31 |
+
},
|
| 32 |
+
"pad_token": {
|
| 33 |
+
"content": "<|▁pad▁|>",
|
| 34 |
+
"lstrip": false,
|
| 35 |
+
"normalized": false,
|
| 36 |
+
"rstrip": false,
|
| 37 |
+
"single_word": false
|
| 38 |
+
}
|
| 39 |
+
}
|
src/unlimited_ocr/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unlimited-OCR MLX — Improved inference pipeline for Apple Silicon."""
|
| 2 |
+
|
| 3 |
+
__version__ = "0.2.0"
|
| 4 |
+
|
| 5 |
+
from unlimited_ocr.pipeline import BatchProcessingError, OCRPipeline
|
| 6 |
+
from unlimited_ocr.engine import OCREngine
|
| 7 |
+
from unlimited_ocr.profiles import get_profile, list_profiles, PROFILES
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"OCRPipeline",
|
| 11 |
+
"BatchProcessingError",
|
| 12 |
+
"OCREngine",
|
| 13 |
+
"get_profile",
|
| 14 |
+
"list_profiles",
|
| 15 |
+
"PROFILES",
|
| 16 |
+
"__version__",
|
| 17 |
+
]
|
src/unlimited_ocr/adapter_registry.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"version": "1.0",
|
| 3 |
+
"description": "LoRA adapter registry for Unlimited-OCR",
|
| 4 |
+
"adapters": {
|
| 5 |
+
"table": {
|
| 6 |
+
"path": "adapters/table-v1",
|
| 7 |
+
"hf_repo": "AutomatosX/unlimited-ocr-lora-table",
|
| 8 |
+
"description": "Table structure recognition adapter"
|
| 9 |
+
},
|
| 10 |
+
"receipt": {
|
| 11 |
+
"path": "adapters/receipt-v1",
|
| 12 |
+
"hf_repo": "AutomatosX/unlimited-ocr-lora-receipt",
|
| 13 |
+
"description": "Receipt and invoice parsing adapter"
|
| 14 |
+
}
|
| 15 |
+
},
|
| 16 |
+
"default": null
|
| 17 |
+
}
|
src/unlimited_ocr/cli.py
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Command-line interface for Unlimited-OCR MLX."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import click
|
| 8 |
+
from rich.console import Console
|
| 9 |
+
|
| 10 |
+
from unlimited_ocr import __version__
|
| 11 |
+
from unlimited_ocr.engine import DEFAULT_MODEL
|
| 12 |
+
from unlimited_ocr.pdf import DEFAULT_PDF_DPI
|
| 13 |
+
from unlimited_ocr.preprocessing import PREPROCESS_MODES
|
| 14 |
+
from unlimited_ocr.profiles import PROFILES
|
| 15 |
+
|
| 16 |
+
console = Console()
|
| 17 |
+
status_console = Console(stderr=True)
|
| 18 |
+
|
| 19 |
+
PROFILE_NAMES = list(PROFILES.keys())
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _resolve_adapter(name_or_path: str) -> str:
|
| 23 |
+
"""Resolve an adapter name to a path using the registry."""
|
| 24 |
+
import json
|
| 25 |
+
|
| 26 |
+
# If it's already a path that exists, use directly
|
| 27 |
+
p = Path(name_or_path).expanduser()
|
| 28 |
+
if p.is_dir():
|
| 29 |
+
return str(p)
|
| 30 |
+
if p.exists():
|
| 31 |
+
raise click.ClickException(f"Adapter path is not a directory: {p}")
|
| 32 |
+
|
| 33 |
+
# Try registry lookup
|
| 34 |
+
registry_paths = (
|
| 35 |
+
Path(__file__).with_name("adapter_registry.json"),
|
| 36 |
+
Path(__file__).parent.parent.parent / "adapters" / "registry.json",
|
| 37 |
+
)
|
| 38 |
+
registry_path = next((path for path in registry_paths if path.is_file()), None)
|
| 39 |
+
if registry_path is not None:
|
| 40 |
+
try:
|
| 41 |
+
registry = json.loads(registry_path.read_text(encoding="utf-8"))
|
| 42 |
+
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
| 43 |
+
raise click.ClickException(
|
| 44 |
+
f"Cannot read adapter registry: {registry_path}"
|
| 45 |
+
) from exc
|
| 46 |
+
adapters = registry.get("adapters", {})
|
| 47 |
+
if name_or_path in adapters:
|
| 48 |
+
entry = adapters[name_or_path]
|
| 49 |
+
if not isinstance(entry, dict):
|
| 50 |
+
raise click.ClickException(
|
| 51 |
+
f"Invalid registry entry for adapter '{name_or_path}'"
|
| 52 |
+
)
|
| 53 |
+
relative_path = entry.get("path")
|
| 54 |
+
adapter_path = (
|
| 55 |
+
Path(__file__).parent.parent.parent / relative_path
|
| 56 |
+
if isinstance(relative_path, str)
|
| 57 |
+
else None
|
| 58 |
+
)
|
| 59 |
+
if adapter_path is not None and adapter_path.is_dir():
|
| 60 |
+
return str(adapter_path)
|
| 61 |
+
# Try HF download
|
| 62 |
+
hf_repo = entry.get("hf_repo")
|
| 63 |
+
if hf_repo:
|
| 64 |
+
try:
|
| 65 |
+
from huggingface_hub import snapshot_download
|
| 66 |
+
return snapshot_download(
|
| 67 |
+
hf_repo,
|
| 68 |
+
allow_patterns=["adapter_config.json", "adapters.safetensors"],
|
| 69 |
+
)
|
| 70 |
+
except Exception as exc:
|
| 71 |
+
raise click.ClickException(
|
| 72 |
+
f"Could not download adapter '{name_or_path}' from {hf_repo}: {exc}"
|
| 73 |
+
) from exc
|
| 74 |
+
raise click.ClickException(
|
| 75 |
+
f"Adapter '{name_or_path}' has no local files or download repository"
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
raise click.ClickException(
|
| 79 |
+
f"Unknown adapter or missing adapter directory: {name_or_path}"
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
@click.group(invoke_without_command=True)
|
| 84 |
+
@click.pass_context
|
| 85 |
+
@click.version_option(version=__version__, prog_name="unlimited-ocr")
|
| 86 |
+
def main(ctx):
|
| 87 |
+
"""Unlimited-OCR MLX — One-shot document parsing on Apple Silicon.
|
| 88 |
+
|
| 89 |
+
Run OCR on images, PDFs, or entire directories using the Unlimited-OCR
|
| 90 |
+
vision-language model optimized for MLX (MX FP8 quantized, ~3.6 GB).
|
| 91 |
+
|
| 92 |
+
\b
|
| 93 |
+
Examples:
|
| 94 |
+
unlimited-ocr run image.jpg
|
| 95 |
+
unlimited-ocr run document.pdf --format markdown -o output.md
|
| 96 |
+
unlimited-ocr run ./scans/ --format json --preprocess -o results/
|
| 97 |
+
unlimited-ocr run image.jpg --grounding --format json
|
| 98 |
+
"""
|
| 99 |
+
if ctx.invoked_subcommand is None:
|
| 100 |
+
click.echo(ctx.get_help())
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
@main.command()
|
| 104 |
+
@click.argument("input_path", type=click.Path(exists=True))
|
| 105 |
+
@click.option("-o", "--output", "output_path", type=click.Path(), default=None,
|
| 106 |
+
help="Output file or directory path.")
|
| 107 |
+
@click.option("-f", "--format", "fmt", type=click.Choice(["text", "markdown", "json", "structured"]),
|
| 108 |
+
default="text", help="Output format.")
|
| 109 |
+
@click.option("-t", "--task", type=click.Choice([
|
| 110 |
+
"document", "markdown", "figure", "free", "multi_page",
|
| 111 |
+
]),
|
| 112 |
+
default="document", help="OCR task type.")
|
| 113 |
+
@click.option("-p", "--profile", type=click.Choice(PROFILE_NAMES),
|
| 114 |
+
default="accurate", help="Generation profile.")
|
| 115 |
+
@click.option("--grounding", is_flag=True, default=False,
|
| 116 |
+
help="Enable bounding box detection.")
|
| 117 |
+
@click.option("--preprocess", is_flag=True, default=False,
|
| 118 |
+
help="Enable image preprocessing.")
|
| 119 |
+
@click.option("--preprocess-mode", type=click.Choice(PREPROCESS_MODES),
|
| 120 |
+
default="document", help="Preprocessing mode.")
|
| 121 |
+
@click.option("--dpi", type=click.IntRange(min=1), default=DEFAULT_PDF_DPI,
|
| 122 |
+
show_default=True,
|
| 123 |
+
help="DPI for PDF rendering (150 is usually enough; model input is 1024px).")
|
| 124 |
+
@click.option("--max-tokens", type=click.IntRange(min=1), default=None,
|
| 125 |
+
help="Maximum tokens per page (overrides profile default).")
|
| 126 |
+
@click.option("--model-path", type=str, default=DEFAULT_MODEL,
|
| 127 |
+
help="Model path (HF repo ID or local directory).")
|
| 128 |
+
@click.option("--verbose", is_flag=True, default=False,
|
| 129 |
+
help="Enable verbose output.")
|
| 130 |
+
@click.option("--stream", is_flag=True, default=False,
|
| 131 |
+
help="Stream output token-by-token (single image only).")
|
| 132 |
+
@click.option("--jobs", type=click.IntRange(min=1), default=1,
|
| 133 |
+
help="Parallel preprocessing workers for batch mode.")
|
| 134 |
+
@click.option("--adapter", type=str, default=None,
|
| 135 |
+
help="LoRA adapter name or path (e.g. 'table' or 'adapters/table-v1').")
|
| 136 |
+
def run(input_path, output_path, fmt, task, profile, grounding, preprocess,
|
| 137 |
+
preprocess_mode, dpi, max_tokens, model_path, verbose, stream, jobs, adapter):
|
| 138 |
+
"""Run OCR on an image, PDF, or directory of images.
|
| 139 |
+
|
| 140 |
+
INPUT_PATH can be an image file, a PDF file, or a directory of images.
|
| 141 |
+
"""
|
| 142 |
+
from unlimited_ocr.pipeline import OCRPipeline
|
| 143 |
+
|
| 144 |
+
input_path = Path(input_path)
|
| 145 |
+
|
| 146 |
+
if stream:
|
| 147 |
+
if input_path.is_dir():
|
| 148 |
+
raise click.UsageError("--stream only supports a single image")
|
| 149 |
+
if output_path is not None:
|
| 150 |
+
raise click.UsageError("--stream writes to stdout and cannot be used with --output")
|
| 151 |
+
if fmt != "text":
|
| 152 |
+
raise click.UsageError("--stream only supports --format text")
|
| 153 |
+
if input_path.suffix.lower() == ".pdf":
|
| 154 |
+
raise click.UsageError("--stream does not support PDF files")
|
| 155 |
+
if jobs != 1:
|
| 156 |
+
raise click.UsageError("--jobs only applies to directory batch processing")
|
| 157 |
+
elif not input_path.is_dir() and jobs != 1:
|
| 158 |
+
raise click.UsageError("--jobs only applies to directory batch processing")
|
| 159 |
+
|
| 160 |
+
if verbose:
|
| 161 |
+
status_console.print(f"[bold]Unlimited-OCR MLX[/] v{__version__}")
|
| 162 |
+
status_console.print(f"[dim]Model:[/] {model_path}")
|
| 163 |
+
status_console.print(f"[dim]Input:[/] {input_path}")
|
| 164 |
+
status_console.print(f"[dim]Profile:[/] {profile} | Format: {fmt} | Task: {task}")
|
| 165 |
+
if preprocess:
|
| 166 |
+
status_console.print(f"[dim]Preprocessing:[/] {preprocess_mode}")
|
| 167 |
+
if adapter:
|
| 168 |
+
status_console.print(f"[dim]Adapter:[/] {adapter}")
|
| 169 |
+
status_console.print()
|
| 170 |
+
|
| 171 |
+
pipeline = None
|
| 172 |
+
try:
|
| 173 |
+
# Resolve adapter path from registry if name given
|
| 174 |
+
adapter_path = _resolve_adapter(adapter) if adapter else None
|
| 175 |
+
|
| 176 |
+
pipeline = OCRPipeline(
|
| 177 |
+
model_path=model_path,
|
| 178 |
+
verbose=verbose,
|
| 179 |
+
preprocess=preprocess,
|
| 180 |
+
preprocess_mode=preprocess_mode,
|
| 181 |
+
profile=profile,
|
| 182 |
+
adapter_path=adapter_path,
|
| 183 |
+
)
|
| 184 |
+
if input_path.is_dir():
|
| 185 |
+
# Batch mode
|
| 186 |
+
_run_batch(pipeline, input_path, output_path, fmt, task,
|
| 187 |
+
grounding, max_tokens, profile, jobs)
|
| 188 |
+
elif stream:
|
| 189 |
+
# Streaming mode (single file, stdout only)
|
| 190 |
+
_run_stream(pipeline, input_path, task, grounding, dpi, max_tokens, profile)
|
| 191 |
+
else:
|
| 192 |
+
# Single file mode (image or PDF)
|
| 193 |
+
_run_single(pipeline, input_path, output_path, fmt, task,
|
| 194 |
+
grounding, dpi, max_tokens, profile)
|
| 195 |
+
except click.ClickException:
|
| 196 |
+
raise
|
| 197 |
+
except Exception as exc:
|
| 198 |
+
if verbose:
|
| 199 |
+
raise
|
| 200 |
+
raise click.ClickException(str(exc)) from exc
|
| 201 |
+
finally:
|
| 202 |
+
if pipeline is not None:
|
| 203 |
+
pipeline.cleanup()
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def _run_stream(pipeline, input_path, task, grounding, dpi, max_tokens, profile):
|
| 207 |
+
"""Handle streaming output for a single file."""
|
| 208 |
+
import sys
|
| 209 |
+
for chunk, result in pipeline.run_stream(
|
| 210 |
+
input_path=input_path,
|
| 211 |
+
task=task,
|
| 212 |
+
grounding=grounding,
|
| 213 |
+
dpi=dpi,
|
| 214 |
+
max_tokens=max_tokens,
|
| 215 |
+
profile=profile,
|
| 216 |
+
):
|
| 217 |
+
if result is None:
|
| 218 |
+
sys.stdout.write(chunk)
|
| 219 |
+
sys.stdout.flush()
|
| 220 |
+
else:
|
| 221 |
+
sys.stdout.write("\n")
|
| 222 |
+
sys.stdout.flush()
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def _run_single(pipeline, input_path, output_path, fmt, task, grounding, dpi, max_tokens, profile):
|
| 226 |
+
"""Handle single file (image or PDF) processing."""
|
| 227 |
+
result = pipeline.run(
|
| 228 |
+
input_path=input_path,
|
| 229 |
+
task=task,
|
| 230 |
+
format=fmt,
|
| 231 |
+
grounding=grounding,
|
| 232 |
+
dpi=dpi,
|
| 233 |
+
max_tokens=max_tokens,
|
| 234 |
+
output_path=output_path,
|
| 235 |
+
profile=profile,
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
if output_path:
|
| 239 |
+
status_console.print(f"[green]Result saved to:[/] {output_path}")
|
| 240 |
+
else:
|
| 241 |
+
# Print to stdout
|
| 242 |
+
click.echo(result)
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def _run_batch(pipeline, input_dir, output_path, fmt, task, grounding, max_tokens, profile, jobs=1):
|
| 246 |
+
"""Handle batch directory processing."""
|
| 247 |
+
results = pipeline.run_batch(
|
| 248 |
+
input_dir=input_dir,
|
| 249 |
+
task=task,
|
| 250 |
+
format=fmt,
|
| 251 |
+
grounding=grounding,
|
| 252 |
+
max_tokens=max_tokens,
|
| 253 |
+
output_dir=output_path,
|
| 254 |
+
show_progress=True,
|
| 255 |
+
profile=profile,
|
| 256 |
+
raise_on_error=True,
|
| 257 |
+
jobs=jobs,
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
if output_path:
|
| 261 |
+
status_console.print(f"\n[green]Processed {len(results)} file(s) → {output_path}[/]")
|
| 262 |
+
elif fmt in {"json", "structured"}:
|
| 263 |
+
import json
|
| 264 |
+
|
| 265 |
+
click.echo(json.dumps(
|
| 266 |
+
{filename: json.loads(output) for filename, output in results.items()},
|
| 267 |
+
indent=2,
|
| 268 |
+
ensure_ascii=False,
|
| 269 |
+
))
|
| 270 |
+
else:
|
| 271 |
+
# Print all results to stdout
|
| 272 |
+
for filename, text in results.items():
|
| 273 |
+
click.echo(f"\n═══ {filename} ═══\n")
|
| 274 |
+
click.echo(text)
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
@main.command()
|
| 278 |
+
@click.argument("pdf_path", type=click.Path(exists=True))
|
| 279 |
+
def info(pdf_path):
|
| 280 |
+
"""Show information about a PDF file (page count, sizes, metadata)."""
|
| 281 |
+
from unlimited_ocr.pdf import get_pdf_info
|
| 282 |
+
|
| 283 |
+
try:
|
| 284 |
+
pdf_info = get_pdf_info(pdf_path)
|
| 285 |
+
except Exception as exc:
|
| 286 |
+
raise click.ClickException(str(exc)) from exc
|
| 287 |
+
console.print(f"[bold]PDF Info:[/] {pdf_path}")
|
| 288 |
+
console.print(f" Pages: {pdf_info['num_pages']}")
|
| 289 |
+
|
| 290 |
+
if pdf_info["page_sizes"]:
|
| 291 |
+
first = pdf_info["page_sizes"][0]
|
| 292 |
+
console.print(f" Page size: {first['width']:.0f} × {first['height']:.0f} pts")
|
| 293 |
+
|
| 294 |
+
meta = pdf_info.get("metadata", {})
|
| 295 |
+
if meta.get("title"):
|
| 296 |
+
console.print(f" Title: {meta['title']}")
|
| 297 |
+
if meta.get("author"):
|
| 298 |
+
console.print(f" Author: {meta['author']}")
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
@main.command()
|
| 302 |
+
def models():
|
| 303 |
+
"""Show available model information."""
|
| 304 |
+
console.print("[bold]Available Models:[/]\n")
|
| 305 |
+
console.print(f" [green]•[/] {DEFAULT_MODEL}")
|
| 306 |
+
console.print(" MX FP8 quantized (~3.6 GB) — recommended for Apple Silicon")
|
| 307 |
+
console.print()
|
| 308 |
+
console.print("[dim]Use --model-path to specify a different model.[/]")
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
@main.command()
|
| 312 |
+
def profiles():
|
| 313 |
+
"""Show available generation profiles."""
|
| 314 |
+
from unlimited_ocr.profiles import list_profiles
|
| 315 |
+
|
| 316 |
+
console.print("[bold]Generation Profiles:[/]\n")
|
| 317 |
+
for p in list_profiles():
|
| 318 |
+
console.print(f" [green]•[/] [bold]{p['name']}[/]")
|
| 319 |
+
console.print(f" {p['description']}")
|
| 320 |
+
console.print(f" [dim]max_tokens={p['max_tokens']}, temp={p['temperature']}, "
|
| 321 |
+
f"ngram={p['no_repeat_ngram_size']}[/]")
|
| 322 |
+
console.print()
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
@main.command()
|
| 326 |
+
@click.option("--host", type=str, default="127.0.0.1", show_default=True,
|
| 327 |
+
help="Bind host. Use 0.0.0.0 only to expose the API on your network.")
|
| 328 |
+
@click.option("--port", type=click.IntRange(1, 65535), default=8000,
|
| 329 |
+
show_default=True, help="Bind port.")
|
| 330 |
+
@click.option("--model-path", type=str, default=DEFAULT_MODEL,
|
| 331 |
+
help="Model path (HF repo ID or local directory).")
|
| 332 |
+
@click.option("--verbose", is_flag=True, default=False,
|
| 333 |
+
help="Enable verbose output.")
|
| 334 |
+
def serve(host, port, model_path, verbose):
|
| 335 |
+
"""Start the HTTP API server.
|
| 336 |
+
|
| 337 |
+
Requires: pip install unlimited-ocr[server]
|
| 338 |
+
|
| 339 |
+
\b
|
| 340 |
+
Endpoints:
|
| 341 |
+
POST /ocr — Run OCR on uploaded image/PDF
|
| 342 |
+
POST /ocr/stream — SSE streaming OCR
|
| 343 |
+
GET /health — Model status
|
| 344 |
+
"""
|
| 345 |
+
try:
|
| 346 |
+
from unlimited_ocr.server import run_server
|
| 347 |
+
except ImportError as exc:
|
| 348 |
+
raise click.ClickException(
|
| 349 |
+
"Server dependencies not installed. Run: pip install unlimited-ocr[server]"
|
| 350 |
+
) from exc
|
| 351 |
+
|
| 352 |
+
console.print(f"[bold]Starting Unlimited-OCR server[/] on {host}:{port}")
|
| 353 |
+
console.print(f"[dim]Model:[/] {model_path}")
|
| 354 |
+
console.print("[dim]Endpoints:[/] POST /ocr, POST /ocr/stream, GET /health")
|
| 355 |
+
console.print()
|
| 356 |
+
|
| 357 |
+
if not host.strip():
|
| 358 |
+
raise click.UsageError("--host must not be empty")
|
| 359 |
+
try:
|
| 360 |
+
run_server(host=host, port=port, model_path=model_path, verbose=verbose)
|
| 361 |
+
except Exception as exc:
|
| 362 |
+
if verbose:
|
| 363 |
+
raise
|
| 364 |
+
raise click.ClickException(str(exc)) from exc
|
| 365 |
+
|
| 366 |
+
|
| 367 |
+
if __name__ == "__main__":
|
| 368 |
+
main()
|
src/unlimited_ocr/engine.py
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Core MLX-VLM inference engine for Unlimited-OCR."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import re
|
| 6 |
+
import time
|
| 7 |
+
import math
|
| 8 |
+
from numbers import Real
|
| 9 |
+
from dataclasses import dataclass, field
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from typing import Optional, Union
|
| 12 |
+
|
| 13 |
+
from PIL import Image
|
| 14 |
+
from rich.console import Console
|
| 15 |
+
|
| 16 |
+
from unlimited_ocr.profiles import GenerationProfile, get_profile
|
| 17 |
+
|
| 18 |
+
console = Console(stderr=True)
|
| 19 |
+
|
| 20 |
+
# Paths for disk images; PIL for in-memory preprocessed / PDF-rendered pages.
|
| 21 |
+
ImageInput = Union[str, Path, Image.Image]
|
| 22 |
+
|
| 23 |
+
DEFAULT_MODEL = "AutomatosX/AX-Unlimited-OCR-3B-MoE-MLX-MXFP8"
|
| 24 |
+
|
| 25 |
+
# Prompt templates following DeepSeek-OCR / Unlimited-OCR vocabulary
|
| 26 |
+
PROMPTS = {
|
| 27 |
+
"document": "<image>document parsing.",
|
| 28 |
+
"markdown": "<image>Convert the document to markdown.",
|
| 29 |
+
"figure": "<image>Parse the figure.",
|
| 30 |
+
"free": "<image>Free OCR.",
|
| 31 |
+
"multi_page": "<image>Multi page parsing.",
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
GROUNDING_PREFIX = "<|grounding|>"
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
@dataclass
|
| 38 |
+
class OCRResult:
|
| 39 |
+
"""Result from a single OCR inference."""
|
| 40 |
+
|
| 41 |
+
text: str
|
| 42 |
+
prompt_used: str
|
| 43 |
+
image_path: str
|
| 44 |
+
tokens_generated: int = 0
|
| 45 |
+
grounding: bool = False
|
| 46 |
+
bboxes: list[dict] = field(default_factory=list)
|
| 47 |
+
# Metadata
|
| 48 |
+
elapsed_seconds: float = 0.0
|
| 49 |
+
profile_name: str = "accurate"
|
| 50 |
+
model_path: str = DEFAULT_MODEL
|
| 51 |
+
preprocessing_applied: list[str] = field(default_factory=list)
|
| 52 |
+
generation_tps: float = 0.0
|
| 53 |
+
peak_memory_gb: float = 0.0
|
| 54 |
+
|
| 55 |
+
def __post_init__(self):
|
| 56 |
+
if self.grounding and self.text and not self.bboxes:
|
| 57 |
+
self.bboxes = parse_grounding(self.text)
|
| 58 |
+
|
| 59 |
+
def metadata(self) -> dict:
|
| 60 |
+
"""Return inference metadata as a dict."""
|
| 61 |
+
return {
|
| 62 |
+
"model": self.model_path,
|
| 63 |
+
"profile": self.profile_name,
|
| 64 |
+
"preprocessing": self.preprocessing_applied,
|
| 65 |
+
"output_tokens": self.tokens_generated,
|
| 66 |
+
"elapsed_seconds": round(self.elapsed_seconds, 2),
|
| 67 |
+
"generation_tps": round(self.generation_tps, 1),
|
| 68 |
+
"peak_memory_gb": round(self.peak_memory_gb, 2),
|
| 69 |
+
"grounding": self.grounding,
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
@dataclass
|
| 74 |
+
class EngineConfig:
|
| 75 |
+
"""Configuration for the OCR engine."""
|
| 76 |
+
|
| 77 |
+
model_path: str = DEFAULT_MODEL
|
| 78 |
+
# ``None`` means to use the selected profile. Explicit values are
|
| 79 |
+
# constructor-level overrides that apply to every inference call.
|
| 80 |
+
max_tokens: Optional[int] = None
|
| 81 |
+
temperature: Optional[float] = None
|
| 82 |
+
verbose: bool = False
|
| 83 |
+
profile: Optional[str] = "accurate"
|
| 84 |
+
# No-repeat ngram suppression (ported from upstream)
|
| 85 |
+
no_repeat_ngram_size: Optional[int] = None
|
| 86 |
+
ngram_window: int = 128
|
| 87 |
+
# LoRA adapter path (None = no adapter)
|
| 88 |
+
adapter_path: Optional[str] = None
|
| 89 |
+
|
| 90 |
+
def get_generation_profile(self) -> GenerationProfile:
|
| 91 |
+
"""Resolve the active generation profile."""
|
| 92 |
+
return get_profile(self.profile)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def parse_grounding(text: str) -> list[dict]:
|
| 96 |
+
"""Parse bounding boxes from grounding output.
|
| 97 |
+
|
| 98 |
+
Unlimited-OCR grounding format (after token decoding):
|
| 99 |
+
<|det|>label [x1, y1, x2, y2]<|/det|>content
|
| 100 |
+
The sentencepiece space token (U+0120 / Ġ) may appear as separator.
|
| 101 |
+
Coordinates are in 0-1000 normalized space.
|
| 102 |
+
The first detection block may lack an opening <|det|> tag.
|
| 103 |
+
Blocks are separated by Ċ (newline token).
|
| 104 |
+
"""
|
| 105 |
+
# Normalize tokenizer-visible whitespace markers.
|
| 106 |
+
normalized = text.replace("\u0120", " ").replace("Ċ", "\n")
|
| 107 |
+
number = r"-?\d+(?:\.\d+)?"
|
| 108 |
+
coordinate_pattern = re.compile(
|
| 109 |
+
rf"\[\s*({number})\s*,\s*({number})\s*,\s*"
|
| 110 |
+
rf"({number})\s*,\s*({number})\s*\]"
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
def coordinate_from_match(match: re.Match) -> list[int | float]:
|
| 114 |
+
values = [float(match.group(i)) for i in range(1, 5)]
|
| 115 |
+
return [int(value) if value.is_integer() else value for value in values]
|
| 116 |
+
|
| 117 |
+
parsed: list[tuple[int, dict]] = []
|
| 118 |
+
|
| 119 |
+
# Upstream also emits the older ref/det form, whose det span can contain
|
| 120 |
+
# one box or a list of boxes.
|
| 121 |
+
ref_pattern = re.compile(
|
| 122 |
+
r"<\|ref\|>(.*?)<\|/ref\|>\s*<\|det\|>(.*?)<\|/det\|>",
|
| 123 |
+
re.DOTALL,
|
| 124 |
+
)
|
| 125 |
+
ref_spans: list[tuple[int, int]] = []
|
| 126 |
+
for match in ref_pattern.finditer(normalized):
|
| 127 |
+
ref_spans.append(match.span())
|
| 128 |
+
for coordinate_match in coordinate_pattern.finditer(match.group(2)):
|
| 129 |
+
parsed.append((match.start(), {
|
| 130 |
+
"label": match.group(1).strip(),
|
| 131 |
+
"bbox": coordinate_from_match(coordinate_match),
|
| 132 |
+
"text": "",
|
| 133 |
+
}))
|
| 134 |
+
|
| 135 |
+
# Current direct form. The first block may omit its opening det token.
|
| 136 |
+
direct_pattern = re.compile(
|
| 137 |
+
rf"(?:^|<\|det\|>)([^\[\n<]*?)\s*"
|
| 138 |
+
rf"({coordinate_pattern.pattern})\s*<\|/det\|>"
|
| 139 |
+
r"([^\n<]*)",
|
| 140 |
+
re.MULTILINE,
|
| 141 |
+
)
|
| 142 |
+
for match in direct_pattern.finditer(normalized):
|
| 143 |
+
if any(start <= match.start() < end for start, end in ref_spans):
|
| 144 |
+
continue
|
| 145 |
+
coordinate_match = coordinate_pattern.search(match.group(2))
|
| 146 |
+
if coordinate_match is None:
|
| 147 |
+
continue
|
| 148 |
+
parsed.append((match.start(), {
|
| 149 |
+
"label": match.group(1).strip(),
|
| 150 |
+
"bbox": coordinate_from_match(coordinate_match),
|
| 151 |
+
"text": match.group(7).strip(),
|
| 152 |
+
}))
|
| 153 |
+
|
| 154 |
+
parsed.sort(key=lambda item: item[0])
|
| 155 |
+
return [item for _, item in parsed]
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
class SlidingWindowNoRepeatNGramProcessor:
|
| 159 |
+
"""Block repeated n-grams within a bounded recent-token window.
|
| 160 |
+
|
| 161 |
+
``mlx-vlm`` accepts custom logits processors but does not expose a native
|
| 162 |
+
no-repeat-n-gram option. This mirrors Unlimited-OCR's upstream safeguard
|
| 163 |
+
without importing MLX at module import time.
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
def __init__(self, ngram_size: int, window_size: int):
|
| 167 |
+
if not isinstance(ngram_size, int) or isinstance(ngram_size, bool) or ngram_size < 1:
|
| 168 |
+
raise ValueError("ngram_size must be a positive integer")
|
| 169 |
+
if not isinstance(window_size, int) or isinstance(window_size, bool) or window_size < 1:
|
| 170 |
+
raise ValueError("window_size must be a positive integer")
|
| 171 |
+
if window_size < ngram_size:
|
| 172 |
+
raise ValueError("window_size must be at least as large as ngram_size")
|
| 173 |
+
self.ngram_size = ngram_size
|
| 174 |
+
self.window_size = window_size
|
| 175 |
+
|
| 176 |
+
def __call__(self, tokens, logits):
|
| 177 |
+
sequence = tokens.tolist() if hasattr(tokens, "tolist") else list(tokens)
|
| 178 |
+
if sequence and isinstance(sequence[0], list):
|
| 179 |
+
sequence = sequence[0]
|
| 180 |
+
if len(sequence) < self.ngram_size:
|
| 181 |
+
return logits
|
| 182 |
+
|
| 183 |
+
prefix_length = self.ngram_size - 1
|
| 184 |
+
current_prefix = tuple(sequence[-prefix_length:]) if prefix_length else ()
|
| 185 |
+
search_start = max(0, len(sequence) - self.window_size)
|
| 186 |
+
search_end = len(sequence) - self.ngram_size + 1
|
| 187 |
+
banned: set[int] = set()
|
| 188 |
+
for index in range(search_start, search_end):
|
| 189 |
+
ngram = sequence[index:index + self.ngram_size]
|
| 190 |
+
if not prefix_length or tuple(ngram[:-1]) == current_prefix:
|
| 191 |
+
banned.add(ngram[-1])
|
| 192 |
+
|
| 193 |
+
if banned:
|
| 194 |
+
logits[:, list(banned)] = float("-inf")
|
| 195 |
+
return logits
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
class OCREngine:
|
| 199 |
+
"""Core inference engine wrapping mlx-vlm for Unlimited-OCR.
|
| 200 |
+
|
| 201 |
+
Uses lazy loading — the model is loaded on first inference call.
|
| 202 |
+
"""
|
| 203 |
+
|
| 204 |
+
def __init__(self, config: Optional[EngineConfig] = None):
|
| 205 |
+
self.config = config or EngineConfig()
|
| 206 |
+
self._model = None
|
| 207 |
+
self._processor = None
|
| 208 |
+
|
| 209 |
+
@property
|
| 210 |
+
def model(self):
|
| 211 |
+
"""Lazy-load the MLX model."""
|
| 212 |
+
if self._model is None:
|
| 213 |
+
self._load_model()
|
| 214 |
+
return self._model
|
| 215 |
+
|
| 216 |
+
@property
|
| 217 |
+
def processor(self):
|
| 218 |
+
"""Lazy-load the processor."""
|
| 219 |
+
if self._processor is None:
|
| 220 |
+
self._load_model()
|
| 221 |
+
return self._processor
|
| 222 |
+
|
| 223 |
+
def _load_model(self):
|
| 224 |
+
"""Load model and processor from mlx-vlm."""
|
| 225 |
+
from mlx_vlm import load
|
| 226 |
+
|
| 227 |
+
if self.config.verbose:
|
| 228 |
+
console.print(f"[bold blue]Loading model:[/] {self.config.model_path}")
|
| 229 |
+
|
| 230 |
+
load_kwargs = {}
|
| 231 |
+
if self.config.adapter_path is not None:
|
| 232 |
+
if not isinstance(self.config.adapter_path, str) or not self.config.adapter_path:
|
| 233 |
+
raise ValueError("adapter_path must be a non-empty string or None")
|
| 234 |
+
# mlx-vlm applies adapters to the nested language model correctly;
|
| 235 |
+
# mlx-lm's helper alone can target the wrong module tree for VLMs.
|
| 236 |
+
load_kwargs["adapter_path"] = self.config.adapter_path
|
| 237 |
+
self._model, self._processor = load(self.config.model_path, **load_kwargs)
|
| 238 |
+
|
| 239 |
+
if self.config.verbose:
|
| 240 |
+
console.print("[bold green]Model loaded successfully.[/]")
|
| 241 |
+
|
| 242 |
+
def build_prompt(
|
| 243 |
+
self,
|
| 244 |
+
task: str = "document",
|
| 245 |
+
grounding: bool = False,
|
| 246 |
+
custom_prompt: Optional[str] = None,
|
| 247 |
+
) -> str:
|
| 248 |
+
"""Build the prompt string for inference.
|
| 249 |
+
|
| 250 |
+
Args:
|
| 251 |
+
task: One of 'document', 'markdown', 'figure', 'free', 'multi_page'.
|
| 252 |
+
grounding: If True, prefix with grounding token for bounding boxes.
|
| 253 |
+
custom_prompt: Override with a fully custom prompt (must contain <image>).
|
| 254 |
+
"""
|
| 255 |
+
if custom_prompt is not None:
|
| 256 |
+
if not isinstance(custom_prompt, str):
|
| 257 |
+
raise TypeError("custom_prompt must be a string")
|
| 258 |
+
image_count = custom_prompt.count("<image>")
|
| 259 |
+
if image_count != 1:
|
| 260 |
+
raise ValueError(
|
| 261 |
+
"custom_prompt must contain exactly one literal '<image>' token"
|
| 262 |
+
)
|
| 263 |
+
prompt = custom_prompt
|
| 264 |
+
else:
|
| 265 |
+
if task not in PROMPTS:
|
| 266 |
+
available = ", ".join(PROMPTS)
|
| 267 |
+
raise ValueError(f"Unknown task '{task}'. Available: {available}")
|
| 268 |
+
prompt = PROMPTS[task]
|
| 269 |
+
|
| 270 |
+
wants_grounding = grounding or GROUNDING_PREFIX in prompt
|
| 271 |
+
if wants_grounding:
|
| 272 |
+
# Upstream DeepSeek/Unlimited-OCR places grounding after the image
|
| 273 |
+
# marker: ``<image>\n<|grounding|>task``. Normalize misplaced or
|
| 274 |
+
# duplicate copies in custom prompts to that trained format.
|
| 275 |
+
prompt = prompt.replace(GROUNDING_PREFIX, "")
|
| 276 |
+
before, after = prompt.split("<image>", 1)
|
| 277 |
+
after = after.lstrip("\r\n")
|
| 278 |
+
prompt = f"{before}<image>\n{GROUNDING_PREFIX}{after}"
|
| 279 |
+
|
| 280 |
+
return prompt
|
| 281 |
+
|
| 282 |
+
def _validate_generation_settings(
|
| 283 |
+
self,
|
| 284 |
+
tokens: int,
|
| 285 |
+
temperature: float,
|
| 286 |
+
ngram_size: int,
|
| 287 |
+
) -> None:
|
| 288 |
+
"""Validate resolved decoding settings before importing MLX."""
|
| 289 |
+
if not isinstance(tokens, int) or isinstance(tokens, bool) or tokens < 1:
|
| 290 |
+
raise ValueError("max_tokens must be a positive integer")
|
| 291 |
+
if (
|
| 292 |
+
not isinstance(temperature, Real)
|
| 293 |
+
or isinstance(temperature, bool)
|
| 294 |
+
or not math.isfinite(float(temperature))
|
| 295 |
+
or temperature < 0
|
| 296 |
+
):
|
| 297 |
+
raise ValueError("temperature must be non-negative and finite")
|
| 298 |
+
if (
|
| 299 |
+
not isinstance(ngram_size, int)
|
| 300 |
+
or isinstance(ngram_size, bool)
|
| 301 |
+
or ngram_size < 0
|
| 302 |
+
):
|
| 303 |
+
raise ValueError("no_repeat_ngram_size must be a non-negative integer")
|
| 304 |
+
if (
|
| 305 |
+
not isinstance(self.config.ngram_window, int)
|
| 306 |
+
or isinstance(self.config.ngram_window, bool)
|
| 307 |
+
or self.config.ngram_window < 1
|
| 308 |
+
):
|
| 309 |
+
raise ValueError("ngram_window must be a positive integer")
|
| 310 |
+
if ngram_size > self.config.ngram_window:
|
| 311 |
+
raise ValueError(
|
| 312 |
+
"ngram_window must be at least as large as no_repeat_ngram_size"
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
@staticmethod
|
| 316 |
+
def _resolve_image_input(image: ImageInput) -> tuple[Image.Image | str, str]:
|
| 317 |
+
"""Normalize image input for mlx-vlm and metadata.
|
| 318 |
+
|
| 319 |
+
Returns:
|
| 320 |
+
(payload for mlx-vlm, path label for OCRResult.image_path)
|
| 321 |
+
"""
|
| 322 |
+
if isinstance(image, Image.Image):
|
| 323 |
+
# Copy so callers can free their original buffer independently.
|
| 324 |
+
return image.copy(), "<in-memory>"
|
| 325 |
+
if isinstance(image, (str, Path)):
|
| 326 |
+
path = str(image)
|
| 327 |
+
return path, path
|
| 328 |
+
raise TypeError(
|
| 329 |
+
f"image must be a path or PIL.Image, got {type(image).__name__}"
|
| 330 |
+
)
|
| 331 |
+
|
| 332 |
+
def _resolve_generation(
|
| 333 |
+
self,
|
| 334 |
+
task: str,
|
| 335 |
+
grounding: bool,
|
| 336 |
+
custom_prompt: Optional[str],
|
| 337 |
+
max_tokens: Optional[int],
|
| 338 |
+
profile: Optional[str],
|
| 339 |
+
) -> tuple[str, bool, GenerationProfile, dict]:
|
| 340 |
+
"""Build prompt and generation kwargs shared by infer / infer_stream."""
|
| 341 |
+
prompt = self.build_prompt(task=task, grounding=grounding, custom_prompt=custom_prompt)
|
| 342 |
+
effective_grounding = grounding or GROUNDING_PREFIX in prompt
|
| 343 |
+
|
| 344 |
+
selected_profile = profile if profile is not None else self.config.profile
|
| 345 |
+
gen_profile = get_profile(selected_profile)
|
| 346 |
+
if max_tokens is not None:
|
| 347 |
+
tokens = max_tokens
|
| 348 |
+
elif self.config.max_tokens is not None:
|
| 349 |
+
tokens = self.config.max_tokens
|
| 350 |
+
else:
|
| 351 |
+
tokens = gen_profile.max_tokens
|
| 352 |
+
temperature = (
|
| 353 |
+
self.config.temperature
|
| 354 |
+
if self.config.temperature is not None
|
| 355 |
+
else gen_profile.temperature
|
| 356 |
+
)
|
| 357 |
+
ngram_size = (
|
| 358 |
+
self.config.no_repeat_ngram_size
|
| 359 |
+
if self.config.no_repeat_ngram_size is not None
|
| 360 |
+
else gen_profile.no_repeat_ngram_size
|
| 361 |
+
)
|
| 362 |
+
self._validate_generation_settings(tokens, temperature, ngram_size)
|
| 363 |
+
|
| 364 |
+
gen_kwargs = {
|
| 365 |
+
"max_tokens": tokens,
|
| 366 |
+
"temperature": temperature,
|
| 367 |
+
"top_p": gen_profile.top_p,
|
| 368 |
+
"verbose": self.config.verbose,
|
| 369 |
+
}
|
| 370 |
+
if gen_profile.repetition_penalty > 1.0:
|
| 371 |
+
gen_kwargs["repetition_penalty"] = gen_profile.repetition_penalty
|
| 372 |
+
if ngram_size > 0:
|
| 373 |
+
gen_kwargs["logits_processors"] = [
|
| 374 |
+
SlidingWindowNoRepeatNGramProcessor(ngram_size, self.config.ngram_window)
|
| 375 |
+
]
|
| 376 |
+
return prompt, effective_grounding, gen_profile, gen_kwargs
|
| 377 |
+
|
| 378 |
+
def infer(
|
| 379 |
+
self,
|
| 380 |
+
image_path: ImageInput,
|
| 381 |
+
task: str = "document",
|
| 382 |
+
grounding: bool = False,
|
| 383 |
+
custom_prompt: Optional[str] = None,
|
| 384 |
+
max_tokens: Optional[int] = None,
|
| 385 |
+
profile: Optional[str] = None,
|
| 386 |
+
preprocessing_applied: Optional[list[str]] = None,
|
| 387 |
+
) -> OCRResult:
|
| 388 |
+
"""Run OCR inference on a single image.
|
| 389 |
+
|
| 390 |
+
Args:
|
| 391 |
+
image_path: Path to the image file, or an in-memory PIL Image.
|
| 392 |
+
task: OCR task type (document, markdown, figure, free, multi_page).
|
| 393 |
+
grounding: Enable bounding box output.
|
| 394 |
+
custom_prompt: Custom prompt override.
|
| 395 |
+
max_tokens: Override max tokens for this inference.
|
| 396 |
+
profile: Override generation profile for this call.
|
| 397 |
+
preprocessing_applied: List of preprocessing steps applied to the image.
|
| 398 |
+
|
| 399 |
+
Returns:
|
| 400 |
+
OCRResult with extracted text and metadata.
|
| 401 |
+
"""
|
| 402 |
+
image_payload, image_label = self._resolve_image_input(image_path)
|
| 403 |
+
prompt, effective_grounding, gen_profile, gen_kwargs = self._resolve_generation(
|
| 404 |
+
task, grounding, custom_prompt, max_tokens, profile
|
| 405 |
+
)
|
| 406 |
+
|
| 407 |
+
if self.config.verbose:
|
| 408 |
+
console.print(f"[dim]Profile:[/] {gen_profile.name} | [dim]Max tokens:[/] {gen_kwargs['max_tokens']}")
|
| 409 |
+
console.print(f"[dim]Prompt:[/] {prompt}")
|
| 410 |
+
console.print(f"[dim]Image:[/] {image_label}")
|
| 411 |
+
|
| 412 |
+
# Import only after validation so invalid calls do not initialize Metal.
|
| 413 |
+
from mlx_vlm import generate
|
| 414 |
+
|
| 415 |
+
# Resolve lazy model loading before starting the per-inference timer.
|
| 416 |
+
model = self.model
|
| 417 |
+
processor = self.processor
|
| 418 |
+
start_time = time.perf_counter()
|
| 419 |
+
|
| 420 |
+
response = generate(
|
| 421 |
+
model,
|
| 422 |
+
processor,
|
| 423 |
+
prompt=prompt,
|
| 424 |
+
image=[image_payload],
|
| 425 |
+
**gen_kwargs,
|
| 426 |
+
)
|
| 427 |
+
|
| 428 |
+
elapsed = time.perf_counter() - start_time
|
| 429 |
+
|
| 430 |
+
# mlx-vlm generate returns a GenerationResult object
|
| 431 |
+
if isinstance(response, str):
|
| 432 |
+
text = response
|
| 433 |
+
num_tokens = 0
|
| 434 |
+
gen_tps = 0.0
|
| 435 |
+
peak_mem = 0.0
|
| 436 |
+
elif hasattr(response, "text"):
|
| 437 |
+
text = response.text
|
| 438 |
+
num_tokens = getattr(
|
| 439 |
+
response,
|
| 440 |
+
"generation_tokens",
|
| 441 |
+
getattr(response, "tokens_generated", 0),
|
| 442 |
+
) or 0
|
| 443 |
+
gen_tps = getattr(response, "generation_tps", 0.0) or 0.0
|
| 444 |
+
peak_mem = getattr(response, "peak_memory", 0.0) or 0.0
|
| 445 |
+
else:
|
| 446 |
+
raise TypeError(
|
| 447 |
+
"mlx-vlm returned an unsupported generation response "
|
| 448 |
+
f"({type(response).__name__})"
|
| 449 |
+
)
|
| 450 |
+
|
| 451 |
+
if not isinstance(text, str):
|
| 452 |
+
raise TypeError("mlx-vlm returned a non-string text field")
|
| 453 |
+
|
| 454 |
+
return OCRResult(
|
| 455 |
+
text=text,
|
| 456 |
+
prompt_used=prompt,
|
| 457 |
+
image_path=image_label,
|
| 458 |
+
tokens_generated=num_tokens,
|
| 459 |
+
grounding=effective_grounding,
|
| 460 |
+
elapsed_seconds=elapsed,
|
| 461 |
+
profile_name=gen_profile.name,
|
| 462 |
+
model_path=self.config.model_path,
|
| 463 |
+
preprocessing_applied=list(preprocessing_applied or []),
|
| 464 |
+
generation_tps=gen_tps,
|
| 465 |
+
peak_memory_gb=peak_mem,
|
| 466 |
+
)
|
| 467 |
+
|
| 468 |
+
def infer_stream(
|
| 469 |
+
self,
|
| 470 |
+
image_path: ImageInput,
|
| 471 |
+
task: str = "document",
|
| 472 |
+
grounding: bool = False,
|
| 473 |
+
custom_prompt: Optional[str] = None,
|
| 474 |
+
max_tokens: Optional[int] = None,
|
| 475 |
+
profile: Optional[str] = None,
|
| 476 |
+
preprocessing_applied: Optional[list[str]] = None,
|
| 477 |
+
):
|
| 478 |
+
"""Run OCR inference with token-by-token streaming.
|
| 479 |
+
|
| 480 |
+
Yields (chunk_text, is_final) tuples. The final yield contains
|
| 481 |
+
the complete OCRResult as the second element.
|
| 482 |
+
|
| 483 |
+
Usage:
|
| 484 |
+
for chunk, result in engine.infer_stream(image):
|
| 485 |
+
print(chunk, end="") # incremental text
|
| 486 |
+
if result is not None:
|
| 487 |
+
# result is the final OCRResult
|
| 488 |
+
pass
|
| 489 |
+
"""
|
| 490 |
+
image_payload, image_label = self._resolve_image_input(image_path)
|
| 491 |
+
prompt, effective_grounding, gen_profile, gen_kwargs = self._resolve_generation(
|
| 492 |
+
task, grounding, custom_prompt, max_tokens, profile
|
| 493 |
+
)
|
| 494 |
+
# Streaming should not print token noise to the console.
|
| 495 |
+
gen_kwargs["verbose"] = False
|
| 496 |
+
|
| 497 |
+
import mlx_vlm
|
| 498 |
+
|
| 499 |
+
model = self.model
|
| 500 |
+
processor = self.processor
|
| 501 |
+
start_time = time.perf_counter()
|
| 502 |
+
|
| 503 |
+
full_text = ""
|
| 504 |
+
num_tokens = 0
|
| 505 |
+
gen_tps = 0.0
|
| 506 |
+
peak_mem = 0.0
|
| 507 |
+
|
| 508 |
+
for response in mlx_vlm.stream_generate(
|
| 509 |
+
model,
|
| 510 |
+
processor,
|
| 511 |
+
prompt=prompt,
|
| 512 |
+
image=[image_payload],
|
| 513 |
+
**gen_kwargs,
|
| 514 |
+
):
|
| 515 |
+
if isinstance(response, str):
|
| 516 |
+
chunk = response
|
| 517 |
+
elif hasattr(response, "text"):
|
| 518 |
+
chunk = response.text
|
| 519 |
+
else:
|
| 520 |
+
raise TypeError(
|
| 521 |
+
"mlx-vlm returned an unsupported stream response "
|
| 522 |
+
f"({type(response).__name__})"
|
| 523 |
+
)
|
| 524 |
+
if not isinstance(chunk, str):
|
| 525 |
+
raise TypeError("mlx-vlm returned a non-string stream chunk")
|
| 526 |
+
# mlx-vlm's stream contract yields only the detokenizer's latest
|
| 527 |
+
# segment. Treating a chunk as cumulative when it happens to share
|
| 528 |
+
# a prefix with prior output loses repeated text (for example two
|
| 529 |
+
# consecutive identical tokens).
|
| 530 |
+
full_text += chunk
|
| 531 |
+
if chunk:
|
| 532 |
+
yield chunk, None
|
| 533 |
+
|
| 534 |
+
# Capture final stats from last response
|
| 535 |
+
if hasattr(response, "generation_tokens"):
|
| 536 |
+
num_tokens = response.generation_tokens or 0
|
| 537 |
+
if hasattr(response, "generation_tps"):
|
| 538 |
+
gen_tps = response.generation_tps or 0.0
|
| 539 |
+
if hasattr(response, "peak_memory"):
|
| 540 |
+
peak_mem = response.peak_memory or 0.0
|
| 541 |
+
|
| 542 |
+
elapsed = time.perf_counter() - start_time
|
| 543 |
+
|
| 544 |
+
result = OCRResult(
|
| 545 |
+
text=full_text,
|
| 546 |
+
prompt_used=prompt,
|
| 547 |
+
image_path=image_label,
|
| 548 |
+
tokens_generated=num_tokens,
|
| 549 |
+
grounding=effective_grounding,
|
| 550 |
+
elapsed_seconds=elapsed,
|
| 551 |
+
profile_name=gen_profile.name,
|
| 552 |
+
model_path=self.config.model_path,
|
| 553 |
+
preprocessing_applied=list(preprocessing_applied or []),
|
| 554 |
+
generation_tps=gen_tps,
|
| 555 |
+
peak_memory_gb=peak_mem,
|
| 556 |
+
)
|
| 557 |
+
yield "", result
|
| 558 |
+
|
| 559 |
+
def is_loaded(self) -> bool:
|
| 560 |
+
"""Check if the model is currently loaded."""
|
| 561 |
+
return self._model is not None
|
| 562 |
+
|
| 563 |
+
def unload(self):
|
| 564 |
+
"""Release model from memory."""
|
| 565 |
+
self._model = None
|
| 566 |
+
self._processor = None
|
src/unlimited_ocr/output.py
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Output formatters for OCR results (text, markdown, JSON, structured)."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
import re
|
| 7 |
+
from dataclasses import dataclass, field
|
| 8 |
+
from datetime import datetime, timezone
|
| 9 |
+
from html.parser import HTMLParser
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from typing import Optional
|
| 12 |
+
|
| 13 |
+
from unlimited_ocr.engine import OCRResult
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@dataclass
|
| 17 |
+
class PageResult:
|
| 18 |
+
"""OCR result for a single page/image."""
|
| 19 |
+
|
| 20 |
+
page_number: int
|
| 21 |
+
image_path: str
|
| 22 |
+
text: str
|
| 23 |
+
bboxes: list[dict] = field(default_factory=list)
|
| 24 |
+
tokens_generated: int = 0
|
| 25 |
+
generation_tps: float = 0.0
|
| 26 |
+
peak_memory_gb: float = 0.0
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
@dataclass
|
| 30 |
+
class DocumentResult:
|
| 31 |
+
"""Aggregated OCR result for a full document (multi-page)."""
|
| 32 |
+
|
| 33 |
+
source: str
|
| 34 |
+
pages: list[PageResult] = field(default_factory=list)
|
| 35 |
+
grounding: bool = False
|
| 36 |
+
format: str = "text"
|
| 37 |
+
timestamp: str = field(
|
| 38 |
+
default_factory=lambda: datetime.now(timezone.utc).isoformat()
|
| 39 |
+
)
|
| 40 |
+
# Inference metadata
|
| 41 |
+
model_path: str = ""
|
| 42 |
+
profile_name: str = "accurate"
|
| 43 |
+
preprocessing_applied: list[str] = field(default_factory=list)
|
| 44 |
+
total_elapsed_seconds: float = 0.0
|
| 45 |
+
|
| 46 |
+
@property
|
| 47 |
+
def full_text(self) -> str:
|
| 48 |
+
"""Concatenated text from all pages."""
|
| 49 |
+
return "\n\n".join(p.text for p in self.pages)
|
| 50 |
+
|
| 51 |
+
@property
|
| 52 |
+
def num_pages(self) -> int:
|
| 53 |
+
return len(self.pages)
|
| 54 |
+
|
| 55 |
+
@classmethod
|
| 56 |
+
def from_ocr_results(
|
| 57 |
+
cls,
|
| 58 |
+
results: list[OCRResult],
|
| 59 |
+
source: str,
|
| 60 |
+
grounding: bool = False,
|
| 61 |
+
) -> "DocumentResult":
|
| 62 |
+
"""Create a DocumentResult from a list of OCRResult objects."""
|
| 63 |
+
pages = [
|
| 64 |
+
PageResult(
|
| 65 |
+
page_number=i + 1,
|
| 66 |
+
image_path=r.image_path,
|
| 67 |
+
text=r.text,
|
| 68 |
+
bboxes=r.bboxes,
|
| 69 |
+
tokens_generated=r.tokens_generated,
|
| 70 |
+
generation_tps=r.generation_tps,
|
| 71 |
+
peak_memory_gb=r.peak_memory_gb,
|
| 72 |
+
)
|
| 73 |
+
for i, r in enumerate(results)
|
| 74 |
+
]
|
| 75 |
+
# Aggregate metadata from all pages. Auto preprocessing can choose
|
| 76 |
+
# different steps for different images.
|
| 77 |
+
model_path = results[0].model_path if results else ""
|
| 78 |
+
profile_name = results[0].profile_name if results else "accurate"
|
| 79 |
+
preprocessing = list(dict.fromkeys(
|
| 80 |
+
step
|
| 81 |
+
for result in results
|
| 82 |
+
for step in result.preprocessing_applied
|
| 83 |
+
))
|
| 84 |
+
total_elapsed = sum(r.elapsed_seconds for r in results)
|
| 85 |
+
|
| 86 |
+
return cls(
|
| 87 |
+
source=source,
|
| 88 |
+
pages=pages,
|
| 89 |
+
grounding=grounding or any(result.grounding for result in results),
|
| 90 |
+
model_path=model_path, profile_name=profile_name,
|
| 91 |
+
preprocessing_applied=preprocessing,
|
| 92 |
+
total_elapsed_seconds=total_elapsed,
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def format_text(result: DocumentResult) -> str:
|
| 97 |
+
"""Format as plain text with page separators."""
|
| 98 |
+
if result.num_pages == 1:
|
| 99 |
+
return result.pages[0].text
|
| 100 |
+
|
| 101 |
+
parts = []
|
| 102 |
+
for page in result.pages:
|
| 103 |
+
parts.append(f"--- Page {page.page_number} ---\n\n{page.text}")
|
| 104 |
+
return "\n\n".join(parts)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def format_markdown(result: DocumentResult) -> str:
|
| 108 |
+
"""Format as Markdown with page headings."""
|
| 109 |
+
parts = []
|
| 110 |
+
source_name = Path(result.source).stem
|
| 111 |
+
|
| 112 |
+
if result.num_pages > 1:
|
| 113 |
+
parts.append(f"# {source_name}\n")
|
| 114 |
+
|
| 115 |
+
for page in result.pages:
|
| 116 |
+
if result.num_pages > 1:
|
| 117 |
+
parts.append(f"## Page {page.page_number}\n")
|
| 118 |
+
parts.append(page.text)
|
| 119 |
+
|
| 120 |
+
return "\n".join(parts)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def format_json(result: DocumentResult, indent: int = 2) -> str:
|
| 124 |
+
"""Format as structured JSON with metadata and optional bounding boxes."""
|
| 125 |
+
output = {
|
| 126 |
+
"source": result.source,
|
| 127 |
+
"num_pages": result.num_pages,
|
| 128 |
+
"grounding": result.grounding,
|
| 129 |
+
"timestamp": result.timestamp,
|
| 130 |
+
"metadata": {
|
| 131 |
+
"model": result.model_path,
|
| 132 |
+
"profile": result.profile_name,
|
| 133 |
+
"preprocessing": result.preprocessing_applied,
|
| 134 |
+
"total_elapsed_seconds": round(result.total_elapsed_seconds, 2),
|
| 135 |
+
},
|
| 136 |
+
"pages": [],
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
for page in result.pages:
|
| 140 |
+
page_data = {
|
| 141 |
+
"page_number": page.page_number,
|
| 142 |
+
"image_path": page.image_path,
|
| 143 |
+
"text": page.text,
|
| 144 |
+
"tokens_generated": page.tokens_generated,
|
| 145 |
+
"generation_tps": round(page.generation_tps, 1),
|
| 146 |
+
"peak_memory_gb": round(page.peak_memory_gb, 2),
|
| 147 |
+
}
|
| 148 |
+
if result.grounding and page.bboxes:
|
| 149 |
+
page_data["bounding_boxes"] = page.bboxes
|
| 150 |
+
output["pages"].append(page_data)
|
| 151 |
+
|
| 152 |
+
return json.dumps(output, indent=indent, ensure_ascii=False)
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
@dataclass
|
| 156 |
+
class TextBlock:
|
| 157 |
+
"""A block of plain text content."""
|
| 158 |
+
|
| 159 |
+
text: str
|
| 160 |
+
block_type: str = "paragraph" # paragraph, heading, list_item, caption
|
| 161 |
+
order: int = 0
|
| 162 |
+
bbox: Optional[list] = None
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
@dataclass
|
| 166 |
+
class TableBlock:
|
| 167 |
+
"""A parsed table extracted from OCR output."""
|
| 168 |
+
|
| 169 |
+
html: str
|
| 170 |
+
rows: list[list[str]] = field(default_factory=list)
|
| 171 |
+
order: int = 0
|
| 172 |
+
bbox: Optional[list] = None
|
| 173 |
+
|
| 174 |
+
@property
|
| 175 |
+
def num_rows(self) -> int:
|
| 176 |
+
return len(self.rows)
|
| 177 |
+
|
| 178 |
+
@property
|
| 179 |
+
def num_cols(self) -> int:
|
| 180 |
+
return max((len(r) for r in self.rows), default=0)
|
| 181 |
+
|
| 182 |
+
def to_csv(self, delimiter: str = ",") -> str:
|
| 183 |
+
"""Convert table to CSV string."""
|
| 184 |
+
import csv
|
| 185 |
+
import io
|
| 186 |
+
buf = io.StringIO()
|
| 187 |
+
writer = csv.writer(buf, delimiter=delimiter)
|
| 188 |
+
for row in self.rows:
|
| 189 |
+
writer.writerow(row)
|
| 190 |
+
return buf.getvalue()
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
@dataclass
|
| 194 |
+
class StructuredPage:
|
| 195 |
+
"""Structured representation of a page with separated content types."""
|
| 196 |
+
|
| 197 |
+
page_number: int
|
| 198 |
+
text_blocks: list[TextBlock] = field(default_factory=list)
|
| 199 |
+
tables: list[TableBlock] = field(default_factory=list)
|
| 200 |
+
raw_text: str = ""
|
| 201 |
+
reading_order: list[dict] = field(default_factory=list)
|
| 202 |
+
|
| 203 |
+
@property
|
| 204 |
+
def plain_text(self) -> str:
|
| 205 |
+
"""Reconstruct plain text in reading order."""
|
| 206 |
+
parts = []
|
| 207 |
+
for item in sorted(
|
| 208 |
+
[(b.order, b.text) for b in self.text_blocks]
|
| 209 |
+
+ [(t.order, t.html) for t in self.tables],
|
| 210 |
+
key=lambda x: x[0],
|
| 211 |
+
):
|
| 212 |
+
parts.append(item[1])
|
| 213 |
+
return "\n\n".join(parts) if parts else self.raw_text
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def parse_tables_from_text(text: str) -> tuple[list[TextBlock], list[TableBlock]]:
|
| 217 |
+
"""Parse OCR text into text blocks and table blocks.
|
| 218 |
+
|
| 219 |
+
Detects HTML <table>...</table> regions and separates them from
|
| 220 |
+
surrounding text content.
|
| 221 |
+
"""
|
| 222 |
+
text_blocks: list[TextBlock] = []
|
| 223 |
+
table_blocks: list[TableBlock] = []
|
| 224 |
+
order = 0
|
| 225 |
+
|
| 226 |
+
def add_text_blocks(value: str) -> None:
|
| 227 |
+
nonlocal order
|
| 228 |
+
for block_text in re.split(r"\n\s*\n", value):
|
| 229 |
+
block_text = block_text.strip()
|
| 230 |
+
if block_text:
|
| 231 |
+
text_blocks.append(TextBlock(
|
| 232 |
+
text=block_text,
|
| 233 |
+
block_type=_classify_text_block(block_text),
|
| 234 |
+
order=order,
|
| 235 |
+
))
|
| 236 |
+
order += 1
|
| 237 |
+
|
| 238 |
+
cursor = 0
|
| 239 |
+
for start, end in _html_table_spans(text):
|
| 240 |
+
add_text_blocks(text[cursor:start])
|
| 241 |
+
table_html = text[start:end]
|
| 242 |
+
table_blocks.append(TableBlock(
|
| 243 |
+
html=table_html,
|
| 244 |
+
rows=_parse_html_table(table_html),
|
| 245 |
+
order=order,
|
| 246 |
+
))
|
| 247 |
+
order += 1
|
| 248 |
+
cursor = end
|
| 249 |
+
add_text_blocks(text[cursor:])
|
| 250 |
+
|
| 251 |
+
return text_blocks, table_blocks
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def _html_table_spans(text: str) -> list[tuple[int, int]]:
|
| 255 |
+
"""Locate balanced outer HTML table regions, including nested tables."""
|
| 256 |
+
tag_pattern = re.compile(r"</?table\b[^>]*>", re.IGNORECASE)
|
| 257 |
+
spans: list[tuple[int, int]] = []
|
| 258 |
+
depth = 0
|
| 259 |
+
start: Optional[int] = None
|
| 260 |
+
for match in tag_pattern.finditer(text):
|
| 261 |
+
closing = bool(re.match(r"<\s*/", match.group(0)))
|
| 262 |
+
if closing:
|
| 263 |
+
if depth == 0:
|
| 264 |
+
continue
|
| 265 |
+
depth -= 1
|
| 266 |
+
if depth == 0 and start is not None:
|
| 267 |
+
spans.append((start, match.end()))
|
| 268 |
+
start = None
|
| 269 |
+
else:
|
| 270 |
+
if depth == 0:
|
| 271 |
+
start = match.start()
|
| 272 |
+
depth += 1
|
| 273 |
+
return spans
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
class _TableHTMLParser(HTMLParser):
|
| 277 |
+
"""Extract first-level table rows and cells from forgiving HTML."""
|
| 278 |
+
|
| 279 |
+
def __init__(self):
|
| 280 |
+
super().__init__(convert_charrefs=True)
|
| 281 |
+
self.table_depth = 0
|
| 282 |
+
self.rows: list[list[str]] = []
|
| 283 |
+
self._row: Optional[list[str]] = None
|
| 284 |
+
self._cell_parts: Optional[list[str]] = None
|
| 285 |
+
|
| 286 |
+
@staticmethod
|
| 287 |
+
def _clean_cell(parts: list[str]) -> str:
|
| 288 |
+
lines = [" ".join(line.split()) for line in "".join(parts).split("\n")]
|
| 289 |
+
return "\n".join(line for line in lines if line).strip()
|
| 290 |
+
|
| 291 |
+
def _finish_cell(self) -> None:
|
| 292 |
+
if self._cell_parts is None:
|
| 293 |
+
return
|
| 294 |
+
if self._row is None:
|
| 295 |
+
self._row = []
|
| 296 |
+
self._row.append(self._clean_cell(self._cell_parts))
|
| 297 |
+
self._cell_parts = None
|
| 298 |
+
|
| 299 |
+
def _finish_row(self) -> None:
|
| 300 |
+
self._finish_cell()
|
| 301 |
+
if self._row:
|
| 302 |
+
self.rows.append(self._row)
|
| 303 |
+
self._row = None
|
| 304 |
+
|
| 305 |
+
def handle_starttag(self, tag: str, attrs) -> None:
|
| 306 |
+
tag = tag.lower()
|
| 307 |
+
if tag == "table":
|
| 308 |
+
self.table_depth += 1
|
| 309 |
+
return
|
| 310 |
+
if self.table_depth != 1:
|
| 311 |
+
return
|
| 312 |
+
if tag == "tr":
|
| 313 |
+
self._finish_row()
|
| 314 |
+
self._row = []
|
| 315 |
+
elif tag in {"td", "th"}:
|
| 316 |
+
self._finish_cell()
|
| 317 |
+
if self._row is None:
|
| 318 |
+
self._row = []
|
| 319 |
+
self._cell_parts = []
|
| 320 |
+
elif tag == "br" and self._cell_parts is not None:
|
| 321 |
+
self._cell_parts.append("\n")
|
| 322 |
+
|
| 323 |
+
def handle_endtag(self, tag: str) -> None:
|
| 324 |
+
tag = tag.lower()
|
| 325 |
+
if tag == "table":
|
| 326 |
+
if self.table_depth == 1:
|
| 327 |
+
self._finish_row()
|
| 328 |
+
if self.table_depth > 0:
|
| 329 |
+
self.table_depth -= 1
|
| 330 |
+
return
|
| 331 |
+
if self.table_depth != 1:
|
| 332 |
+
return
|
| 333 |
+
if tag in {"td", "th"}:
|
| 334 |
+
self._finish_cell()
|
| 335 |
+
elif tag == "tr":
|
| 336 |
+
self._finish_row()
|
| 337 |
+
|
| 338 |
+
def handle_data(self, data: str) -> None:
|
| 339 |
+
if self.table_depth >= 1 and self._cell_parts is not None:
|
| 340 |
+
self._cell_parts.append(data)
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
def _parse_html_table(html: str) -> list[list[str]]:
|
| 344 |
+
"""Extract rows from an HTML table string."""
|
| 345 |
+
parser = _TableHTMLParser()
|
| 346 |
+
parser.feed(html)
|
| 347 |
+
parser.close()
|
| 348 |
+
parser._finish_row()
|
| 349 |
+
return parser.rows
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def _classify_text_block(text: str) -> str:
|
| 353 |
+
"""Heuristically classify a text block type."""
|
| 354 |
+
lines = text.split("\n")
|
| 355 |
+
first_line = lines[0].strip()
|
| 356 |
+
|
| 357 |
+
# Markdown heading
|
| 358 |
+
if first_line.startswith("#"):
|
| 359 |
+
return "heading"
|
| 360 |
+
# List items
|
| 361 |
+
if all(
|
| 362 |
+
line.strip().startswith(("-", "•", "*")) or re.match(r"^\d+[.)]\s", line.strip())
|
| 363 |
+
for line in lines if line.strip()
|
| 364 |
+
):
|
| 365 |
+
return "list_item"
|
| 366 |
+
# Short single line (likely caption or heading)
|
| 367 |
+
if len(lines) == 1 and len(first_line) < 80 and not first_line.endswith("."):
|
| 368 |
+
return "heading"
|
| 369 |
+
return "paragraph"
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
def format_structured(result: DocumentResult, indent: int = 2) -> str:
|
| 373 |
+
"""Format as structured JSON with separated text blocks and tables."""
|
| 374 |
+
output = {
|
| 375 |
+
"source": result.source,
|
| 376 |
+
"num_pages": result.num_pages,
|
| 377 |
+
"grounding": result.grounding,
|
| 378 |
+
"timestamp": result.timestamp,
|
| 379 |
+
"metadata": {
|
| 380 |
+
"model": result.model_path,
|
| 381 |
+
"profile": result.profile_name,
|
| 382 |
+
"preprocessing": result.preprocessing_applied,
|
| 383 |
+
"total_elapsed_seconds": round(result.total_elapsed_seconds, 2),
|
| 384 |
+
},
|
| 385 |
+
"pages": [],
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
for page in result.pages:
|
| 389 |
+
text_blocks, table_blocks = parse_tables_from_text(page.text)
|
| 390 |
+
|
| 391 |
+
page_data = {
|
| 392 |
+
"page_number": page.page_number,
|
| 393 |
+
"image_path": page.image_path,
|
| 394 |
+
"raw_text": page.text,
|
| 395 |
+
"tokens_generated": page.tokens_generated,
|
| 396 |
+
"generation_tps": round(page.generation_tps, 1),
|
| 397 |
+
"peak_memory_gb": round(page.peak_memory_gb, 2),
|
| 398 |
+
"text_blocks": [
|
| 399 |
+
{
|
| 400 |
+
"type": b.block_type,
|
| 401 |
+
"text": b.text,
|
| 402 |
+
"order": b.order,
|
| 403 |
+
**({"bbox": b.bbox} if b.bbox else {}),
|
| 404 |
+
}
|
| 405 |
+
for b in text_blocks
|
| 406 |
+
],
|
| 407 |
+
"tables": [
|
| 408 |
+
{
|
| 409 |
+
"html": t.html,
|
| 410 |
+
"rows": t.rows,
|
| 411 |
+
"num_rows": t.num_rows,
|
| 412 |
+
"num_cols": t.num_cols,
|
| 413 |
+
"order": t.order,
|
| 414 |
+
**({"bbox": t.bbox} if t.bbox else {}),
|
| 415 |
+
}
|
| 416 |
+
for t in table_blocks
|
| 417 |
+
],
|
| 418 |
+
"reading_order": sorted(
|
| 419 |
+
[
|
| 420 |
+
{"kind": "text", "index": index, "order": block.order}
|
| 421 |
+
for index, block in enumerate(text_blocks)
|
| 422 |
+
]
|
| 423 |
+
+ [
|
| 424 |
+
{"kind": "table", "index": index, "order": table.order}
|
| 425 |
+
for index, table in enumerate(table_blocks)
|
| 426 |
+
],
|
| 427 |
+
key=lambda item: item["order"],
|
| 428 |
+
),
|
| 429 |
+
}
|
| 430 |
+
if result.grounding and page.bboxes:
|
| 431 |
+
page_data["bounding_boxes"] = page.bboxes
|
| 432 |
+
output["pages"].append(page_data)
|
| 433 |
+
|
| 434 |
+
return json.dumps(output, indent=indent, ensure_ascii=False)
|
| 435 |
+
|
| 436 |
+
|
| 437 |
+
def format_result(result: DocumentResult, fmt: str = "text") -> str:
|
| 438 |
+
"""Format a DocumentResult in the specified format.
|
| 439 |
+
|
| 440 |
+
Args:
|
| 441 |
+
result: The document OCR result.
|
| 442 |
+
fmt: One of 'text', 'markdown', 'json', 'structured'.
|
| 443 |
+
|
| 444 |
+
Returns:
|
| 445 |
+
Formatted string output.
|
| 446 |
+
"""
|
| 447 |
+
formatters = {
|
| 448 |
+
"text": format_text,
|
| 449 |
+
"markdown": format_markdown,
|
| 450 |
+
"json": format_json,
|
| 451 |
+
"structured": format_structured,
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
formatter = formatters.get(fmt)
|
| 455 |
+
if formatter is None:
|
| 456 |
+
raise ValueError(f"Unknown format '{fmt}'. Choose from: {list(formatters.keys())}")
|
| 457 |
+
|
| 458 |
+
return formatter(result)
|
| 459 |
+
|
| 460 |
+
|
| 461 |
+
def save_result(
|
| 462 |
+
result: DocumentResult,
|
| 463 |
+
output_path: str | Path,
|
| 464 |
+
fmt: Optional[str] = None,
|
| 465 |
+
) -> Path:
|
| 466 |
+
"""Format and save a DocumentResult to a file.
|
| 467 |
+
|
| 468 |
+
Args:
|
| 469 |
+
result: The document OCR result.
|
| 470 |
+
output_path: Path to write the output file.
|
| 471 |
+
fmt: Output format. If None, inferred from file extension.
|
| 472 |
+
|
| 473 |
+
Returns:
|
| 474 |
+
Path to the saved file.
|
| 475 |
+
"""
|
| 476 |
+
output_path = Path(output_path)
|
| 477 |
+
|
| 478 |
+
if fmt is None:
|
| 479 |
+
ext = output_path.suffix.lower()
|
| 480 |
+
fmt_map = {
|
| 481 |
+
".txt": "text",
|
| 482 |
+
".md": "markdown",
|
| 483 |
+
".json": "json",
|
| 484 |
+
}
|
| 485 |
+
fmt = fmt_map.get(ext, "text")
|
| 486 |
+
|
| 487 |
+
content = format_result(result, fmt)
|
| 488 |
+
|
| 489 |
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 490 |
+
output_path.write_text(content, encoding="utf-8")
|
| 491 |
+
|
| 492 |
+
return output_path
|