Skip to content

fix(models): read the nested quantization block in deepseek::ModelArgs - #989

Closed
rapsealk wants to merge 1 commit into
lablup:mainfrom
rapsealk:fix/deepseek-nested-quantization
Closed

rapsealk wants to merge 1 commit into
lablup:mainfrom
rapsealk:fix/deepseek-nested-quantization

Conversation

@rapsealk

Copy link
Copy Markdown
Member

Summary

deepseek::ModelArgs silently dropped the MLX-style nested "quantization": {"group_size": .., "bits": ..} block — the struct only declared flat top-level group_size / bits fields, so the inheritance the three DeepSeek-OCR loaders perform in vlm_deepseekocr.rs accomplished nothing and the accessors always fell back to 64 / 4. This adds the nested field (reusing deepseek_v2::Quantization, whose presence is why the byte-identical inheritance in vlm_deepseek_vl2.rs already worked) and makes group_size() / bits() prefer nested-then-flat-then-default.

Related issues

Closes #975

Type of change

  • fix — bug fix

Test plan

  • New regression tests in src/models/deepseek_tests.rs:
    • model_args_reads_inherited_nested_quantization_block — drives the same language_config insert the OCR loaders perform (including the extra "mode" key real checkpoints carry) and asserts the declared pair survives deserialization.
    • model_args_reads_flat_quantization_keys — the flat spelling keeps loading with its declared values.
    • model_args_nested_quantization_beats_flat_keys — pins the documented precedence.
    • model_args_defaults_without_quantization_declaration — no declaration keeps 64 / 4, which is what the two in-tree DeepSeek-OCR checkpoints rely on.
  • cargo fmt --all -- --check
  • cargo clippy --all-targets --features metal,accelerate -- -D warnings
  • cargo test --profile test-fast --features metal,accelerate models::deepseek / vision::unlimited_ocr — 7 + 3 passed (full --release suite not run on this machine; see notes)
  • Validated with a real checkpoint: not run — no Metal toolchain on this machine (CPU-only build via MLXCEL_BUILD_METAL=OFF). Both in-tree OCR checkpoints declare exactly {group_size: 64, bits: 4}, i.e. the fallback values, so the resolved pair — and therefore OCR output — is unchanged for them by construction. Happy to run the confirmation if a maintainer prefers it before merge.

Notes for reviewers

  • DeepSeekModel::load needs no change: it deserializes the whole config.json into ModelArgs, so a plain deepseek checkpoint's nested block is now picked up through the same field.
  • Issue fix(models): deepseek::ModelArgs silently drops an inherited quantization block #975's optional item 3 (factoring the ~dozen private Quantization copies under src/models/ into one shared struct) is deliberately left out — that's a cross-family refactor and a separate PR per the one-logical-change rule. Reusing deepseek_v2::Quantization here at least stops these two siblings from diverging again.
  • One nuance inherited from deepseek_v2: Quantization's fields are required, so a quantization block missing group_size/bits now fails deserialization instead of being ignored. That matches deepseek_v2's existing behavior for the same block and MLX exports always write both keys.

Checklist

  • PR title uses a Conventional Commits prefix
  • One logical change per PR
  • Updated docs/ — not needed (no user-facing behavior change for in-tree checkpoints)
  • Updated // Used by: ... comments — no shared function modified
  • No secrets, credentials, or .env files committed

🤖 Generated with Claude Code

The three DeepSeek-OCR loaders in vlm_deepseekocr.rs inherit the root
`quantization` object into `language_config` before deserializing into
`deepseek::ModelArgs`, but the struct only declared flat top-level
`group_size` / `bits` fields, so serde silently dropped the nested block
and `group_size()` / `bits()` fell back to the hardcoded 64 / 4
regardless of what the checkpoint declared. `DeepSeekModel::load` had
the same shape for a plain `deepseek` config.json.

Add the nested `quantization` field (reusing `deepseek_v2::Quantization`,
whose presence is why the byte-identical inheritance in the DeepSeek-VL2
loader already worked) and make the accessors prefer it, keeping the
flat spelling as a fallback so checkpoints that rely on it keep loading.

The two in-tree DeepSeek-OCR checkpoints declare exactly the fallback
values, so their behavior is unchanged; a checkpoint declaring anything
else (8-bit, group_size 32/128) now loads with its declared params
instead of silently degrading through the wrong group_size.

Closes lablup#975. Follow-up to lablup#958 (PR lablup#972), which bounded the values the
expert loaders store; this fixes the adjacent problem that the values
reaching them were not the ones the checkpoint declared.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@inureyes

inureyes commented Aug 3, 2026

Copy link
Copy Markdown
Member

Thanks for catching this, the diagnosis here is exactly right and matches what #975 describes.

I have opened #1016 for the same issue and it supersedes this PR, so I wanted to explain rather than leave it hanging. Three things pushed it toward a separate branch instead of building on this one:

It is also a fork branch, so completing it in place would have meant force-pushing over your work.

For what it is worth, the real-checkpoint runs confirmed your reasoning about the in-tree checkpoints: models/deepseek-ocr-4bit and models/deepseek-ocr-2-4bit both declare exactly {group_size: 64, bits: 4}, and their OCR output is byte-identical before and after.

@inureyes

inureyes commented Aug 3, 2026

Copy link
Copy Markdown
Member

Closing as superseded by #1016, which merged as a86e6340a. The diagnosis in this PR was correct and is credited in the comment above; the separate branch was needed because this one predates #1006 (the quantization-mode allowlist) and because completing it in place would have meant force-pushing over your fork. Thanks for finding it.

@inureyes inureyes closed this Aug 3, 2026
@rapsealk
rapsealk deleted the fix/deepseek-nested-quantization branch August 4, 2026 00:53
@inureyes inureyes added type:bug Bug fixes, error corrections, or issue resolutions priority:medium Medium priority labels Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority:medium Medium priority type:bug Bug fixes, error corrections, or issue resolutions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(models): deepseek::ModelArgs silently drops an inherited quantization block

2 participants