DeepSeek-VL Hybrid¶
The dual-tower DeepSeek-VL variant, ported to pure Keras 3. It runs the SigLIP
tower alongside a high-resolution SAM-style tower (high_res_* arguments)
and fuses both feature streams before the text decoder, which is what lets it
read fine detail such as small text.
Build note: the high-res tower only builds when image inputs are passed, so a text-only forward leaves those sublayers unbuilt.
Links:
See also deepseek_vl.md, janus.md.
Variants¶
Load any of these with from_weights("<variant>").
| Variant | Hub |
|---|---|
deepseek_vl_7b_chat |
kerasformers release |
deepseek_vl_7b_base |
kerasformers release |
API¶
DeepseekVLHybridModel¶
DeepSeek-VL Hybrid (7B) backbone: dual vision (SigLIP @384 + SAM @1024) + 3-way aligner + Llama-7B decoder.
| Arg | Default | Meaning |
|---|---|---|
vocab_size |
102400 |
token vocabulary size |
embed_dim |
4096 |
text model width |
mlp_dim |
11008 |
MLP inner width |
num_layers |
30 |
decoder blocks |
num_heads |
32 |
query heads |
num_kv_heads |
32 |
key/value heads (GQA) |
head_dim |
128 |
per-head width |
norm_eps |
1e-06 |
normalization epsilon |
rope_theta |
10000.0 |
rotary base frequency |
tie_embeddings |
False |
reuse embeddings as the LM head |
vision_embed_dim |
1024 |
vision tower width |
vision_mlp_dim |
4096 |
vision MLP width |
vision_num_layers |
24 |
vision tower depth |
vision_num_heads |
16 |
vision attention heads |
image_size |
384 |
expected image resolution |
patch_size |
16 |
patch size |
vision_norm_eps |
1e-06 |
vision tower norm epsilon |
high_res_embed_dim |
768 |
|
high_res_mlp_dim |
3072 |
|
high_res_num_layers |
12 |
|
high_res_num_heads |
12 |
|
high_res_image_size |
1024 |
|
high_res_patch_size |
16 |
|
high_res_output_channels |
256 |
|
high_res_window_size |
14 |
|
high_res_global_attn_indexes |
(2, 5, 8, 11) |
|
high_res_norm_eps |
1e-06 |
|
image_token_id |
100015 |
placeholder token id expanded per image |
DeepseekVLHybridGenerate¶
DeepSeek-VL Hybrid with an LM head + fast .generate().
generate(input_ids, attention_mask=None, max_new_tokens=None,
eos_token_id=None, sampler=None, seed=None, **prefill_inputs)
Image and video tensors ride along as **prefill_inputs; the processor
produces them for you.
DeepseekVLHybridImageProcessor¶
Dual-resolution preprocessing for DeepSeek-VL Hybrid (7B).
| Arg | Default | Meaning |
|---|---|---|
size |
384 |
target resolution |
high_res_size |
1024 |
|
min_size |
14 |
smallest allowed edge |
image_mean |
(0.5, 0.5, 0.5) |
per-channel normalization mean |
image_std |
(0.5, 0.5, 0.5) |
per-channel normalization std |
high_res_image_mean |
(0.48145466, 0.4578275, 0.40821073) |
|
high_res_image_std |
(0.26862954, 0.26130258, 0.27577711) |
|
background_color |
(127, 127, 127) |
pad colour for letterboxing |
high_res_background_color |
(122, 116, 104) |
|
data_format |
None |
channels_last or channels_first |
DeepseekVLHybridProcessor¶
Image + text -> model inputs for DeepSeek-VL Hybrid (7B).
| Arg | Default | Meaning |
|---|---|---|
variant |
None |
variant whose tokenizer/processor files to fetch |
hf_id |
None |
Hub repo to pull tokenizer/processor files from |
num_image_tokens |
576 |
tokens each image expands to |
tokenizer |
None |
override the default tokenizer |
image_processor |
None |
override the default image processor |
DeepseekVLHybridTokenizer¶
DeepSeek-VL Hybrid (7B) tokenizer.
| Arg | Default | Meaning |
|---|---|---|
variant |
None |
variant whose tokenizer/processor files to fetch |
hf_id |
None |
Hub repo to pull tokenizer/processor files from |
tokenizer_file |
None |
explicit path to a tokenizer.json |
End-to-end example¶
Single input (image + text)¶
import os
os.environ["KERAS_BACKEND"] = "torch" # or "jax" / "tensorflow"
from PIL import Image
from kerasformers.models.deepseek_vl_hybrid import DeepseekVLHybridGenerate, DeepseekVLHybridProcessor
model = DeepseekVLHybridGenerate.from_weights("deepseek_vl_7b_chat")
processor = DeepseekVLHybridProcessor.from_weights("deepseek_vl_7b_chat")
image = Image.open("photo.jpg")
inputs = processor(conversation=[{
"role": "user",
"content": [
{"type": "image", "image": image},
{"type": "text", "text": "Describe this image in one sentence."},
],
}])
outputs = model.generate(**inputs, max_new_tokens=64)
print(processor.decode(outputs[0]))
Several images in one conversation¶
Add one image content item per image. The processor expands each marker to that image's own patch count:
inputs = processor(conversation=[{
"role": "user",
"content": [
{"type": "image", "image": Image.open("a.jpg")},
{"type": "image", "image": Image.open("b.jpg")},
{"type": "text", "text": "What differs between these two images?"},
],
}])
outputs = model.generate(**inputs, max_new_tokens=64)
Batch¶
Pass a list of conversations. Each one is rendered separately and takes only the images its own markers claim, so the conversations do not need the same number of images or images of the same size:
conversations = [
[{"role": "user", "content": [
{"type": "image", "image": Image.open("a.jpg")},
{"type": "text", "text": "What is in this image?"}]}],
[{"role": "user", "content": [
{"type": "image", "image": Image.open("b.jpg")},
{"type": "image", "image": Image.open("c.jpg")},
{"type": "text", "text": "What differs between these?"}]}],
]
inputs = processor(conversation=conversations)
outputs = model.generate(**inputs, max_new_tokens=64)
for text in processor.batch_decode(outputs):
print(text)
Text-only prompts batch the same way: pass text=[...] with no images.
Text only¶
DeepseekVLHybridTokenizer encodes raw text: it has no chat template, so pass a prompt you
have rendered yourself (or go through the processor above).
from kerasformers.models.deepseek_vl_hybrid import DeepseekVLHybridTokenizer
tokenizer = DeepseekVLHybridTokenizer.from_weights("deepseek_vl_7b_chat")
inputs = tokenizer("Who wrote Dune?")
outputs = model.generate(**inputs, max_new_tokens=32)
print(tokenizer.decode(outputs[0]))
Lower memory¶
Larger checkpoints load in bf16 or weight-only quantized. See quantization.md: