Repository navigation
Conversation
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>
|
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: |
|
Closing as superseded by #1016, which merged as |
Summary
deepseek::ModelArgssilently dropped the MLX-style nested"quantization": {"group_size": .., "bits": ..}block — the struct only declared flat top-levelgroup_size/bitsfields, so the inheritance the three DeepSeek-OCR loaders perform invlm_deepseekocr.rsaccomplished nothing and the accessors always fell back to64/4. This adds the nested field (reusingdeepseek_v2::Quantization, whose presence is why the byte-identical inheritance invlm_deepseek_vl2.rsalready worked) and makesgroup_size()/bits()prefer nested-then-flat-then-default.Related issues
Closes #975
Type of change
fix— bug fixTest plan
src/models/deepseek_tests.rs:model_args_reads_inherited_nested_quantization_block— drives the samelanguage_configinsert 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 -- --checkcargo clippy --all-targets --features metal,accelerate -- -D warningscargo test --profile test-fast --features metal,accelerate models::deepseek/vision::unlimited_ocr— 7 + 3 passed (full--releasesuite not run on this machine; see notes)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::loadneeds no change: it deserializes the wholeconfig.jsonintoModelArgs, so a plaindeepseekcheckpoint's nested block is now picked up through the same field.Quantizationcopies undersrc/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. Reusingdeepseek_v2::Quantizationhere at least stops these two siblings from diverging again.deepseek_v2:Quantization's fields are required, so aquantizationblock missinggroup_size/bitsnow fails deserialization instead of being ignored. That matchesdeepseek_v2's existing behavior for the same block and MLX exports always write both keys.Checklist
docs/— not needed (no user-facing behavior change for in-tree checkpoints)// Used by: ...comments — no shared function modified.envfiles committed🤖 Generated with Claude Code