Skip to content

[Bugfix][Frontend][Model] Per-model serving defaults, DeepSeek-OCR resolution modes, and SWA async-eviction fix (SM70) - #292

Closed
kkobold wants to merge 5 commits into
1CatAI:mainfrom
kkobold:pub/ocr-serving-defaults
Closed

kkobold wants to merge 5 commits into
1CatAI:mainfrom
kkobold:pub/ocr-serving-defaults

Conversation

@kkobold

@kkobold kkobold commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

What

Serving-quality improvements for long-generation OCR workloads, plus a
correctness fix for prefix-anchored SWA:

  1. Bugfix (prefix-anchored SWA): out-of-window gap blocks were freed
    on the scheduled-token basis; under async scheduling this can free
    blocks the worker has not yet computed past. Free on the
    processed-token basis instead. Regression tests cover the async gap.
  2. Per-model serving defaults with request-override bounds: an
    architecture-keyed registry applied at the chat endpoint. A model
    declares its recommended sampling/extra-args recipe; requests may
    override within declared bounds. DeepSeek-OCR registers its checkpoint
    README recipe (n-gram blocker 30/90 with whitelist tokens, temperature
    0, max_tokens 8192) so an unconfigured client gets the model's intended
    behavior via plain chat completions.
  3. Architecture-keyed chat template fallbacks: DeepSeek-OCR's
    model_type previously fell through to the DeepSeek-VL2 template, which
    injects role markers the OCR checkpoint was not trained with; the
    fallback now pins the raw-concatenation prompt (golden test included).
  4. Repetition-detection default for OCR serving: a bounded
    exact-pattern detector as a safety net for degenerate table pages,
    request-overridable.
  5. Per-request DeepSeek-OCR resolution modes: the checkpoint's five
    official modes (tiny/small/base/large/gundam) selectable via
    mm_processor_kwargs: {"image_mode": ...}; multi-image serving
    profile; max_crops plumbing. The serving default remains the
    checkpoint's gundam configuration.

Why

On document OCR, the model's own recipe is load-bearing: without the
n-gram blocker and the correct raw prompt, long pages degenerate into
loops. These changes make the endpoint serve the checkpoint's intended
recipe by default while keeping every knob per-request-overridable within
bounds. The SWA fix closes an async-scheduling correctness gap in that
capability (#281).

Validation

All device work on Tesla V100-SXM2 32 GB (sm_70), fp16, on current main.

  • 195 CPU tests green, including the prefix-anchored SWA
    admission/mask/manager suites, the new eviction-basis regression tests,
    resolution-mode suite, template golden, defaults/bounds and
    repetition-detection suites.
  • Endpoint (serve) on V100: deterministic 3x byte-identical generations;
    resolution-mode discrimination (default = gundam as identity control;
    base/large produce distinct image-token counts); document-page scoring
    against known ground truth per mode.

Serving DeepSeek-OCR (validated configuration)

Launch line used for every endpoint validation in this PR (Tesla
V100-SXM2 32 GB, fp16):

vllm serve /path/to/DeepSeek-OCR \
  --dtype float16 \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.85 \
  --enforce-eager \
  --no-enable-prefix-caching \
  --mm-processor-cache-gb 0 \
  --logits-processors vllm.model_executor.models.deepseek_ocr:NGramPerReqLogitsProcessor
  • --logits-processors loads the n-gram blocker class; the serving-defaults
    registry supplies its per-request parameters and fails startup with the
    exact flag text if the class is missing
    (never silent looping).
  • --no-enable-prefix-caching / --mm-processor-cache-gb 0 are the
    checkpoint recipe's engine flags.
  • For multi-image requests add --limit-mm-per-prompt '{"image":8}'
    (the SM70 baseline defaults to 1 image per prompt).
  • --enforce-eager is the validated configuration; CUDA graphs measured
    ~4x faster decode at ~1% window cost and can be enabled after a
    graph-mode validation pass.
  • Tensor parallelism: the checkpoint has 10 attention heads, so
    --tensor-parallel-size must divide 10 (1 or 2 on a 4-GPU host); 4
    fails at startup with the head-divisibility error.

Merge base

Branch applies and merges cleanly onto current main at 34403018d
([Kernel][SM70] Optimize E4M3 page800 long decode, #290); all validation in
this PR ran on that base.

kkobold added 5 commits August 25, 2026 09:03
…nder async scheduling

With async scheduling / PP, `num_computed_tokens` optimistically includes
tokens of steps whose output is not yet processed; their attention windows
still read the blocks just below the optimistic boundary, and rejected
speculative tokens can roll it back. Freeing recycling-aware KV (sliding
window, chunked-local, prefix-anchored SWA gap eviction) on that optimistic
basis can release blocks an in-flight step still reads.

Track `Request.num_in_flight_tokens` in the scheduler and free on
`num_computed_tokens - num_in_flight_tokens` in `allocate_slots` and in the
pre-connector prune. Thread the renamed `max_in_flight_tokens` bound
(`max_concurrent_batches * max_num_batched_tokens`) through the KV cache
manager/coordinator and the per-request admission caps so startup pool
sizing reserves for overlapping batches.

Tests: in-flight accounting, sliding-window/chunked-local/prefix-anchored
frees deferred until the in-flight step settles (and unchanged under sync
scheduling), admission caps under overlapping batches, and the
connector-finish prune on the settled basis.

Signed-off-by: kkobold <sabbaghdanilo@gmai.com>
Some models are only correct when served with a specific decoding recipe;
DeepSeek-OCR's documentation makes its anti-repetition logits processor
mandatory for long documents and requires `skip_special_tokens=False` with
greedy decoding. Serving without the recipe produces documented looping
with no visible error.

Add a serving-layer registry keyed by model architecture (extensible by
out-of-tree plugins at import time): registered sampling defaults flow
through the existing `default_sampling_params` channel (with
`skip_special_tokens` now tri-state on the chat request so a model default
can apply), recipe `extra_args` (the processor parameters) are filled in
for requests that omit them, and client-supplied `vllm_xargs` overrides are
validated against registered bounds — out-of-range values return HTTP 400
instead of reaching the engine. Startup fails loudly when a
recipe-required logits processor is not loaded. DeepSeek-OCR's recipe is
registered in-tree.

Signed-off-by: kkobold <sabbaghdanilo@gmai.com>
…CR prompt

The DeepSeek-OCR checkpoint reports model_type "deepseek_vl_v2", so the
chat endpoint resolved the DeepSeek-VL2 fallback template, which inserts
chat role markers and silently changes the OCR prompt relative to the
documented recipe — a quality failure with no error.

Add an architecture-keyed fallback map that takes precedence over the
model_type map (public registration for plugins included) and key
DeepseekOCRForCausalLM to the raw-concatenation OCR template; genuine
DeepSeek-VL2 chat checkpoints keep their template. Golden tests pin the
resolution and the byte-exact rendered prompts for both documented OCR
modes, with a negative control proving the goldens discriminate.

Signed-off-by: kkobold <sabbaghdanilo@gmai.com>
Degeneration patterns longer than the n-gram processor's ban window (e.g.
slow-drift empty-table rows) can still bloat output by thousands of junk
tokens. Registered models may now declare repetition_detection defaults,
applied when the request leaves the field unset; a request-supplied value
always wins. DeepSeek-OCR registers a conservative profile grounded in the
observed failure shape (min_count=8 spares legitimate dense tables).

Signed-off-by: kkobold <sabbaghdanilo@gmai.com>
…ving profile

Plumb the official resolution modes (tiny/small/base/large/gundam) through
DeepseekOCRProcessor as a per-request image_mode selector, with crop bound
plumbing (min_crops/max_crops) and an opt-in multi-image crop safeguard.
Token counting moves to a single shared arithmetic
(count_image_tokens_for) used by both the processor pixel path and the
model's placeholder counter, making the producer/counter contract hold by
construction for every mode, crop cap, and image count.

The serving defaults registry gains per-image-count profiles
(sampling_defaults_multi_image / extra_args_defaults_multi_image); the
chat completion path counts request image items and selects the profile
before merging request overrides.

Signed-off-by: kkobold <sabbaghdanilo@gmai.com>
@kkobold kkobold closed this Aug 25, 2026
@kkobold
kkobold deleted the pub/ocr-serving-defaults branch August 25, 2026 12:09
@1CatAI 1CatAI locked and limited conversation to collaborators Aug 26, 2026
@1CatAI 1CatAI unlocked this conversation Aug 27, 2026
@yangzhuxinyzx

Copy link
Copy Markdown
Contributor

已取消归档并解锁,但原作者在归档前删除了外部 head 分支,GitHub 因而无法原编号重开。私有备份中的原始 head SHA 已无改动恢复为公开 Draft 延续 PR:https://github.com/1CatAI/1Cat-vLLM/pull/352。#292 本身保持可见、未锁定和 Closed,以保留原作者关闭记录。

yangzhuxinyzx added a commit that referenced this pull request Aug 28, 2026
…-145100

[Restore] Reconcile audited #292 history with latest main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants