From f84ded620e711a7ca447d52822664358753bf2b9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 1 Nov 2026 20:24:32 +0000 Subject: [PATCH 1/4] feat(models): onboard xiaomi-research/Xiaomi-OCR-0 Add the Xiaomi-OCR-0 catalog entry (Apache-2.0, 0.87B, Qwen3_5ForConditionalGeneration, pinned to bbd8cf8d), mirroring GLM-OCR: - default profile: SGLangVisionExtractAdapter (native SGLang Qwen3.5 architecture, trust_remote_code: false), model-card prompts as task_prompts with document parsing as the default task. - transformers profile: the generic HF reference path (GlmOcrAdapter: AutoProcessor + AutoModelForImageTextToText, no remote code) with the same document prompt. YAML only; no adapter changes. Config tests cover the bundle routing, memory reservation, no-remote-code and prompt/decoding parity between the two profiles. Co-authored-by: Shai Shmuel --- .../models/xiaomi-research__Xiaomi-OCR-0.yaml | 53 +++++++++++++++++++ .../tests/config/test_bundle_coverage.py | 1 + .../config/test_sglang_memory_profiles.py | 9 +++- .../tests/config/test_xiaomi_ocr_profiles.py | 26 +++++++++ 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml create mode 100644 packages/sie_server/tests/config/test_xiaomi_ocr_profiles.py diff --git a/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml b/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml new file mode 100644 index 000000000..558f11112 --- /dev/null +++ b/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml @@ -0,0 +1,53 @@ +sie_id: xiaomi-research/Xiaomi-OCR-0 +hf_id: xiaomi-research/Xiaomi-OCR-0 +hf_revision: bbd8cf8d21f256b3644b44cb860f3a7cecb93c30 +inputs: + text: false + image: true + audio: false + video: false +tasks: + encode: null + score: null + extract: {} +max_sequence_length: 16384 +profiles: + # Plain Hugging Face reference path (Qwen3_5ForConditionalGeneration, native + # in transformers 5; no remote code). The card decodes greedily with 4096 new + # tokens; the checkpoint's own dtype is bfloat16. + transformers: + max_batch_tokens: 16384 + compute_precision: bfloat16 + adapter_path: sie_server.adapters.glm_ocr:GlmOcrAdapter + adapter_options: + loadtime: + # The model card's document-parsing prompt. + user_text: "Extract all information from the main body of the document image and represent it in markdown format, ignoring headers and footers. Tables should be expressed in OTSL format, formulas in the document should be represented using LATEX format, and the parsing should be organized according to the reading order." + runtime: + max_new_tokens: 4096 + num_beams: 1 + default: + max_batch_tokens: 16384 + compute_precision: bfloat16 + adapter_path: sie_server.adapters.sglang_vision_extract.adapter:SGLangVisionExtractAdapter + adapter_options: + loadtime: + # Native SGLang architecture (Qwen3_5ForConditionalGeneration); never + # execute repository code. + trust_remote_code: false + # 64 pages in flight, each up to 16384 tokens of page image and output, + # need far more KV than the automatic one-sequence pool of 16384. + mem_fraction_static: 0.85 + served_model_name: xiaomi-research/Xiaomi-OCR-0 + max_concurrent_requests: 64 + # Model card prompts. The default is document parsing; callers pick + # another region type with the runtime ``task`` option. + default_task: document + task_prompts: + document: "Extract all information from the main body of the document image and represent it in markdown format, ignoring headers and footers. Tables should be expressed in OTSL format, formulas in the document should be represented using LATEX format, and the parsing should be organized according to the reading order." + text: "Extract the text in the image." + table: "Parse the table in the image into OTSL." + formula: "Identify the formula in the image and represent it using LATEX format." + runtime: + max_new_tokens: 4096 + num_beams: 1 diff --git a/packages/sie_server/tests/config/test_bundle_coverage.py b/packages/sie_server/tests/config/test_bundle_coverage.py index 09651d712..09778b47a 100644 --- a/packages/sie_server/tests/config/test_bundle_coverage.py +++ b/packages/sie_server/tests/config/test_bundle_coverage.py @@ -148,6 +148,7 @@ def test_candle_bundle_only_exposes_profile_variants() -> None: ("lightonai/LightOnOCR-2-1B", "transformers5.yaml"), ("PaddlePaddle/PaddleOCR-VL-1.5", "default.yaml"), ("zai-org/GLM-OCR", "transformers5.yaml"), + ("xiaomi-research/Xiaomi-OCR-0", "transformers5.yaml"), ], ) def test_generative_ocr_defaults_to_isolated_sglang_extract_and_keeps_transformers_fallback( diff --git a/packages/sie_server/tests/config/test_sglang_memory_profiles.py b/packages/sie_server/tests/config/test_sglang_memory_profiles.py index c3175e34a..d1de72fe9 100644 --- a/packages/sie_server/tests/config/test_sglang_memory_profiles.py +++ b/packages/sie_server/tests/config/test_sglang_memory_profiles.py @@ -129,7 +129,14 @@ def test_extending_profile_forwards_its_resolved_kv_budget(): @pytest.mark.parametrize( - "name", ["Qwen__Qwen3.5-4B", "lightonai__LightOnOCR-3-4B", "Qwen__Qwen3-VL-8B-Instruct", "zai-org__GLM-OCR"] + "name", + [ + "Qwen__Qwen3.5-4B", + "lightonai__LightOnOCR-3-4B", + "Qwen__Qwen3-VL-8B-Instruct", + "zai-org__GLM-OCR", + "xiaomi-research__Xiaomi-OCR-0", + ], ) def test_specialized_profiles_retain_calibrated_reservation(name): config = load_model_config(MODELS / f"{name}.yaml") diff --git a/packages/sie_server/tests/config/test_xiaomi_ocr_profiles.py b/packages/sie_server/tests/config/test_xiaomi_ocr_profiles.py new file mode 100644 index 000000000..9411414b6 --- /dev/null +++ b/packages/sie_server/tests/config/test_xiaomi_ocr_profiles.py @@ -0,0 +1,26 @@ +"""Xiaomi-OCR-0 serves native weights with the model card's prompts.""" + +from pathlib import Path + +import yaml + +_CONFIG = Path(__file__).resolve().parents[2] / "models" / "xiaomi-research__Xiaomi-OCR-0.yaml" + + +def _profiles() -> dict: + return yaml.safe_load(_CONFIG.read_text())["profiles"] + + +def test_sglang_profile_never_trusts_repository_code() -> None: + default = _profiles()["default"] + assert default["adapter_path"].endswith(":SGLangVisionExtractAdapter") + assert default["adapter_options"]["loadtime"]["trust_remote_code"] is False + + +def test_both_profiles_default_to_the_same_card_prompt_and_decoding() -> None: + profiles = _profiles() + reference = profiles["transformers"]["adapter_options"] + sglang = profiles["default"]["adapter_options"] + task = sglang["loadtime"]["default_task"] + assert sglang["loadtime"]["task_prompts"][task] == reference["loadtime"]["user_text"] + assert sglang["runtime"] == reference["runtime"] == {"max_new_tokens": 4096, "num_beams": 1} From ff69291fc3c09ebb073e9e79cb05aa5f17d75a4d Mon Sep 17 00:00:00 2001 From: Shai Shmuel Date: Mon, 2 Nov 2026 09:02:41 +0200 Subject: [PATCH 2/4] fix(models): bound Xiaomi-OCR-0 page pixels to fit the sequence window At the processor's own limit one page image expands to 16384 visual tokens, which fills max_sequence_length before any output. Cap SGLang's image max_pixels at 12000 merged patches so a full page, the 4096 new tokens and the prompt fit, and pin that budget in a config test. Co-Authored-By: Claude Opus 5.5 --- .../models/xiaomi-research__Xiaomi-OCR-0.yaml | 6 ++++++ .../tests/config/test_xiaomi_ocr_profiles.py | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml b/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml index 558f11112..3402d7909 100644 --- a/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml +++ b/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml @@ -40,6 +40,12 @@ profiles: mem_fraction_static: 0.85 served_model_name: xiaomi-research/Xiaomi-OCR-0 max_concurrent_requests: 64 + # At the processor's own limit one page expands to 16384 visual tokens, + # the whole window. 12000 leaves room for the 4096 new tokens and the + # prompt. + extra_launch_args: + - "--mm-process-config" + - '{"image":{"min_pixels":65536,"max_pixels":12288000}}' # Model card prompts. The default is document parsing; callers pick # another region type with the runtime ``task`` option. default_task: document diff --git a/packages/sie_server/tests/config/test_xiaomi_ocr_profiles.py b/packages/sie_server/tests/config/test_xiaomi_ocr_profiles.py index 9411414b6..2a7c83db6 100644 --- a/packages/sie_server/tests/config/test_xiaomi_ocr_profiles.py +++ b/packages/sie_server/tests/config/test_xiaomi_ocr_profiles.py @@ -1,10 +1,14 @@ """Xiaomi-OCR-0 serves native weights with the model card's prompts.""" +import json +import math from pathlib import Path import yaml _CONFIG = Path(__file__).resolve().parents[2] / "models" / "xiaomi-research__Xiaomi-OCR-0.yaml" +_MERGED_PATCH_PIXELS = 32 * 32 +_PROMPT_TOKENS = 256 def _profiles() -> dict: @@ -24,3 +28,12 @@ def test_both_profiles_default_to_the_same_card_prompt_and_decoding() -> None: task = sglang["loadtime"]["default_task"] assert sglang["loadtime"]["task_prompts"][task] == reference["loadtime"]["user_text"] assert sglang["runtime"] == reference["runtime"] == {"max_new_tokens": 4096, "num_beams": 1} + + +def test_a_full_page_and_its_output_fit_the_sequence_window() -> None: + config = yaml.safe_load(_CONFIG.read_text()) + options = config["profiles"]["default"]["adapter_options"] + args = options["loadtime"]["extra_launch_args"] + max_pixels = json.loads(args[args.index("--mm-process-config") + 1])["image"]["max_pixels"] + page_tokens = math.ceil(max_pixels / _MERGED_PATCH_PIXELS) + assert page_tokens + options["runtime"]["max_new_tokens"] + _PROMPT_TOKENS <= config["max_sequence_length"] From 466e2e60405967278be57ea276cf3152e1aad20c Mon Sep 17 00:00:00 2001 From: Shai Shmuel Date: Mon, 2 Nov 2026 10:05:38 +0200 Subject: [PATCH 3/4] fix(models): serve full-resolution Xiaomi-OCR-0 pages within an L4 budget Replace the page pixel cap with a larger window so pages keep the processor's own resolution: a 16 MP page is 16384 visual tokens, and the window now also holds the card's 4096 new tokens and the prompt. Measured on one L4 with a 4096x4096 dense page: - window 16384: the page is rejected (prompt exceeds the context) - window 24576 at mem_fraction_static 0.85: the vision encoder OOMs (it needs about 3.6 GiB above the static pool for one such page) - window 24576 at 0.70: one page passes; 16 and 32 concurrent pass (32/32 in 190 s, GPU peak 21.1 of 23.0 GB) - 64 concurrent full-size uploads exceed the adapter's /generate write timeout, so concurrency is set to 32 Co-Authored-By: Claude Opus 5.5 --- .../models/xiaomi-research__Xiaomi-OCR-0.yaml | 22 +++++++++---------- .../config/test_sglang_memory_profiles.py | 9 +------- .../tests/config/test_xiaomi_ocr_profiles.py | 22 +++++++++++-------- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml b/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml index 3402d7909..e5afa3c6d 100644 --- a/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml +++ b/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml @@ -10,7 +10,9 @@ tasks: encode: null score: null extract: {} -max_sequence_length: 16384 +# A page at the processor's own pixel limit is 16384 visual tokens; the +# window adds the card's 4096 new tokens and the prompt on top. +max_sequence_length: 24576 profiles: # Plain Hugging Face reference path (Qwen3_5ForConditionalGeneration, native # in transformers 5; no remote code). The card decodes greedily with 4096 new @@ -35,17 +37,15 @@ profiles: # Native SGLang architecture (Qwen3_5ForConditionalGeneration); never # execute repository code. trust_remote_code: false - # 64 pages in flight, each up to 16384 tokens of page image and output, - # need far more KV than the automatic one-sequence pool of 16384. - mem_fraction_static: 0.85 + # Pages in flight, each up to 24576 tokens of page image and output, + # need far more KV than the automatic one-sequence pool of 24576. The + # vision encoder needs about 3.6 GiB above the static pool for one + # 16 MP page; on an L4, 0.85 leaves too little and the engine OOMs. + mem_fraction_static: 0.70 served_model_name: xiaomi-research/Xiaomi-OCR-0 - max_concurrent_requests: 64 - # At the processor's own limit one page expands to 16384 visual tokens, - # the whole window. 12000 leaves room for the 4096 new tokens and the - # prompt. - extra_launch_args: - - "--mm-process-config" - - '{"image":{"min_pixels":65536,"max_pixels":12288000}}' + # 64 full-size uploads at once outrun the adapter's /generate write + # timeout on an L4 worker; 32 was measured clean. + max_concurrent_requests: 32 # Model card prompts. The default is document parsing; callers pick # another region type with the runtime ``task`` option. default_task: document diff --git a/packages/sie_server/tests/config/test_sglang_memory_profiles.py b/packages/sie_server/tests/config/test_sglang_memory_profiles.py index d1de72fe9..c3175e34a 100644 --- a/packages/sie_server/tests/config/test_sglang_memory_profiles.py +++ b/packages/sie_server/tests/config/test_sglang_memory_profiles.py @@ -129,14 +129,7 @@ def test_extending_profile_forwards_its_resolved_kv_budget(): @pytest.mark.parametrize( - "name", - [ - "Qwen__Qwen3.5-4B", - "lightonai__LightOnOCR-3-4B", - "Qwen__Qwen3-VL-8B-Instruct", - "zai-org__GLM-OCR", - "xiaomi-research__Xiaomi-OCR-0", - ], + "name", ["Qwen__Qwen3.5-4B", "lightonai__LightOnOCR-3-4B", "Qwen__Qwen3-VL-8B-Instruct", "zai-org__GLM-OCR"] ) def test_specialized_profiles_retain_calibrated_reservation(name): config = load_model_config(MODELS / f"{name}.yaml") diff --git a/packages/sie_server/tests/config/test_xiaomi_ocr_profiles.py b/packages/sie_server/tests/config/test_xiaomi_ocr_profiles.py index 2a7c83db6..43792eaf3 100644 --- a/packages/sie_server/tests/config/test_xiaomi_ocr_profiles.py +++ b/packages/sie_server/tests/config/test_xiaomi_ocr_profiles.py @@ -1,13 +1,11 @@ """Xiaomi-OCR-0 serves native weights with the model card's prompts.""" -import json -import math from pathlib import Path import yaml _CONFIG = Path(__file__).resolve().parents[2] / "models" / "xiaomi-research__Xiaomi-OCR-0.yaml" -_MERGED_PATCH_PIXELS = 32 * 32 +_PROCESSOR_MAX_PAGE_TOKENS = 16_777_216 // (32 * 32) _PROMPT_TOKENS = 256 @@ -30,10 +28,16 @@ def test_both_profiles_default_to_the_same_card_prompt_and_decoding() -> None: assert sglang["runtime"] == reference["runtime"] == {"max_new_tokens": 4096, "num_beams": 1} -def test_a_full_page_and_its_output_fit_the_sequence_window() -> None: +def test_a_full_resolution_page_and_its_output_fit_the_sequence_window() -> None: config = yaml.safe_load(_CONFIG.read_text()) - options = config["profiles"]["default"]["adapter_options"] - args = options["loadtime"]["extra_launch_args"] - max_pixels = json.loads(args[args.index("--mm-process-config") + 1])["image"]["max_pixels"] - page_tokens = math.ceil(max_pixels / _MERGED_PATCH_PIXELS) - assert page_tokens + options["runtime"]["max_new_tokens"] + _PROMPT_TOKENS <= config["max_sequence_length"] + for profile in config["profiles"].values(): + loadtime = profile["adapter_options"]["loadtime"] + assert "--mm-process-config" not in loadtime.get("extra_launch_args", []) + budget = _PROCESSOR_MAX_PAGE_TOKENS + profile["adapter_options"]["runtime"]["max_new_tokens"] + _PROMPT_TOKENS + assert budget <= config["max_sequence_length"] + + +def test_sglang_profile_keeps_its_l4_calibrated_reservation() -> None: + loadtime = _profiles()["default"]["adapter_options"]["loadtime"] + assert loadtime["mem_fraction_static"] == 0.70 + assert loadtime["max_concurrent_requests"] == 32 From 698cf0c56689e66a5810b4dfe8c261ba485b66fe Mon Sep 17 00:00:00 2001 From: Shai Shmuel Date: Mon, 2 Nov 2026 15:05:16 +0200 Subject: [PATCH 4/4] docs(models): state Xiaomi-OCR-0 memory headroom as measured The static pool is a fraction of free memory at launch, and the rest is runtime headroom. One 16 MP page peaked 3,612 MiB above post-load usage (17,456 -> 21,068 of 23,034 MiB on an L4), which 0.85 does not leave. Co-Authored-By: Claude Opus 5.5 --- .../sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml b/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml index e5afa3c6d..aad0189a0 100644 --- a/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml +++ b/packages/sie_server/models/xiaomi-research__Xiaomi-OCR-0.yaml @@ -38,9 +38,10 @@ profiles: # execute repository code. trust_remote_code: false # Pages in flight, each up to 24576 tokens of page image and output, - # need far more KV than the automatic one-sequence pool of 24576. The - # vision encoder needs about 3.6 GiB above the static pool for one - # 16 MP page; on an L4, 0.85 leaves too little and the engine OOMs. + # need far more KV than the automatic one-sequence pool of 24576. + # SGLang keeps (1 - fraction) of the memory free at launch as runtime + # headroom. One 16 MP page peaks about 3.5 GiB above the memory in use + # after load; on an L4, 0.85 leaves too little and the engine OOMs. mem_fraction_static: 0.70 served_model_name: xiaomi-research/Xiaomi-OCR-0 # 64 full-size uploads at once outrun the adapter's /generate write