
jina-vlm is a token-efficient 2.4B parameter vision-language model that achieves state-of-the-art multilingual VQA performance among open 2B-scale VLMs. The model couples a SigLIP2 vision encoder with a Qwen3 language decoder and makes use of image tiling and attention-pooling for token-efficient processing of arbitrary-resolution images.
| Model | Params | VQA Avg | MMMB | MM-Bench | RealWorld QA |
|---|---|---|---|---|---|
| jina-vlm | 2.4B | 72.3 | 78.8 | 74.3 | 68.2 |
| Qwen2-VL-2B | 2.2B | 66.4 | 71.3 | 69.4 | 62.9 |
| Qwen3-VL-2B | 2.2B | 71.6 | 75.0 | 72.3 | 63.9 |
| InternVL3-2B | 2.2B | 69.2 | 73.6 | 71.9 | 64.3 |
| InternVL3.5-2B | 2.2B | 71.6 | 74.6 | 70.9 | 62.0 |
https://api-beta-vlm.jina.ai. All requests require a Jina API key in the Authorization header, get your API key at jina.ai.| Format | Example |
|---|---|
| HTTP/HTTPS URL | https://example.com/image.jpg |
| Base64 data URI | data:image/jpeg;base64,/9j/4AAQ... |
1curl https://api-beta-vlm.jina.ai/v1/chat/completions \
2 -H "Content-Type: application/json" \
3 -H "Authorization: Bearer $JINA_API_KEY" \
4 -d '{
5 "model": "jina-vlm",
6 "messages": [{
7 "role": "user",
8 "content": [
9 {"type": "text", "text": "Describe this image"},
10 {"type": "image_url", "image_url": {"url": "https://example.com/photo.jpg"}}
11 ]
12 }]
13 }'1curl https://api-beta-vlm.jina.ai/v1/chat/completions \
2 -H "Content-Type: application/json" \
3 -H "Authorization: Bearer $JINA_API_KEY" \
4 -d '{
5 "model": "jina-vlm",
6 "messages": [{
7 "role": "user",
8 "content": [
9 {"type": "text", "text": "What is in this image?"},
10 {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,'$(base64 -i image.jpg)'"}}
11 ]
12 }]
13 }'1curl https://api-beta-vlm.jina.ai/v1/chat/completions \
2 -H "Content-Type: application/json" \
3 -H "Authorization: Bearer $JINA_API_KEY" \
4 -d '{
5 "model": "jina-vlm",
6 "messages": [{"role": "user", "content": "What is the capital of France?"}]
7 }'"stream": true to receive tokens as they're generated:1curl https://api-beta-vlm.jina.ai/v1/chat/completions \
2 -H "Content-Type: application/json" \
3 -H "Authorization: Bearer $JINA_API_KEY" \
4 -d '{
5 "model": "jina-vlm",
6 "stream": true,
7 "messages": [{"role": "user", "content": "Write a haiku about coding"}]
8 }'1{
2 "error": {
3 "message": "Model is loading, please retry in 30-60 seconds. Cold start takes ~30s after the service scales up.",
4 "code": 503
5 }
6}uv syncuv sync --extra flash-attnjina-vlm using the infer.py CLI:1# Single image
2python infer.py -i image.jpg -p "What's in this image?"
3
4# Streaming output
5python infer.py -i image.jpg -p "Describe this image" --stream
6
7# Multiple images
8python infer.py -i img1.jpg -i img2.jpg -p "Compare these images"
9
10# Text-only
11python infer.py -p "What is the capital of France?"-m, --model: Model path. Auto-detects local repo (if config.json exists) or falls back to jinaai/jina-vlm from HuggingFace.-i, --image: Image path, URL, or glob pattern (can specify multiple times).-p, --prompt: Text prompt (can specify multiple times).--max-crops: Maximum crops (default: 12).--max-tokens: Maximum output tokens (default: 1024).--max-pixels: Max pixels per image, larger images are resized preserving aspect ratio.--stream: Enable streaming output.python infer.py -i assets/the_persistence_of_memory.jpg -p "Describe this picture"| Input | Output |
![]() |
|
1import torch
2from transformers import AutoModelForCausalLM, AutoProcessor, GenerationConfig
3
4processor = AutoProcessor.from_pretrained(
5 'jinaai/jina-vlm', use_fast=False, trust_remote_code=True
6)
7model = AutoModelForCausalLM.from_pretrained(
8 'jinaai/jina-vlm',
9 device_map='auto',
10 trust_remote_code=True
11)
12
13image = 'https://picsum.photos/800/600'
14conversation = [
15 {
16 'role': 'user',
17 'content': [
18 {'type': 'image', 'image': image},
19 {'type': 'text', 'text': 'Describe this image'},
20 ],
21 }
22]
23
24text = processor.apply_chat_template(conversation, add_generation_prompt=True)
25inputs = processor(text=[text], images=[image], padding='longest', return_tensors='pt')
26inputs = {k: v.to(model.device) if isinstance(v, torch.Tensor) else v for k, v in inputs.items()}
27
28output = model.generate(
29 **inputs,
30 generation_config=GenerationConfig(max_new_tokens=512, do_sample=False),
31 return_dict_in_generate=True,
32 use_model_defaults=True,
33)
34
35response = processor.tokenizer.decode(
36 output.sequences[0][inputs['input_ids'].shape[-1]:],
37 skip_special_tokens=True
38)
39print(response)1images = ['https://picsum.photos/id/1/800/600', 'https://picsum.photos/id/2/800/600']
2conversation = [
3 {
4 'role': 'user',
5 'content': [
6 {'type': 'image', 'image': images[0]},
7 {'type': 'image', 'image': images[1]},
8 {'type': 'text', 'text': 'What is the difference between these images?'},
9 ],
10 }
11]
12text = processor.apply_chat_template(conversation, add_generation_prompt=True)
13inputs = processor(text=[text], images=images, padding='longest', return_tensors='pt')
14inputs = {k: v.to(model.device) if isinstance(v, torch.Tensor) else v for k, v in inputs.items()}
15
16output = model.generate(
17 **inputs,
18 generation_config=GenerationConfig(max_new_tokens=512, do_sample=False),
19 return_dict_in_generate=True,
20 use_model_defaults=True,
21)
22response = processor.tokenizer.decode(
23 output.sequences[0][inputs['input_ids'].shape[-1]:],
24 skip_special_tokens=True
25)
26print(response)1conversation = [
2 {
3 'role': 'user',
4 'content': [
5 {'type': 'text', 'text': 'Explain quantum computing in simple terms'},
6 ],
7 }
8]
9text = processor.apply_chat_template(conversation, add_generation_prompt=True)
10inputs = processor(text=[text], padding='longest', return_tensors='pt')
11inputs = {k: v.to(model.device) if isinstance(v, torch.Tensor) else v for k, v in inputs.items()}
12
13output = model.generate(
14 **inputs,
15 generation_config=GenerationConfig(max_new_tokens=512, do_sample=False),
16 return_dict_in_generate=True,
17 use_model_defaults=True,
18)
19response = processor.tokenizer.decode(
20 output.sequences[0][inputs['input_ids'].shape[-1]:],
21 skip_special_tokens=True
22)
23print(response)1import torch
2from transformers import AutoModelForCausalLM, AutoProcessor, GenerationConfig
3
4processor = AutoProcessor.from_pretrained(
5 'jinaai/jina-vlm', use_fast=False, trust_remote_code=True
6)
7model = AutoModelForCausalLM.from_pretrained(
8 'jinaai/jina-vlm',
9 device_map='auto',
10 torch_dtype=torch.bfloat16,
11 attn_implementation='flash_attention_2',
12 trust_remote_code=True
13)
14
15images = [
16 'https://picsum.photos/id/22/800/600',
17 'https://picsum.photos/id/49/800/600'
18]
19conversations = [
20 [
21 {
22 'role': 'user',
23 'content': [
24 {'type': 'image', 'image': images[0]},
25 {'type': 'text', 'text': 'What is the man doing in this image?'},
26 ],
27 }
28 ],
29 [
30 {
31 'role': 'user',
32 'content': [
33 {'type': 'image', 'image': images[1]},
34 {'type': 'text', 'text': 'What country\'s flag is in this image?'},
35 ],
36 }
37 ],
38]
39
40texts = processor.apply_chat_template(conversations, add_generation_prompt=True)
41inputs = processor(text=texts, images=images, padding='longest', return_tensors='pt')
42inputs = {k: v.to(model.device) if isinstance(v, torch.Tensor) else v for k, v in inputs.items()}
43
44output = model.generate(
45 **inputs,
46 generation_config=GenerationConfig(max_new_tokens=512, do_sample=False),
47 return_dict_in_generate=True,
48 use_model_defaults=True,
49)
50
51for idx in range(len(output.sequences)):
52 gen_ids = output.sequences[idx][inputs['input_ids'].shape[-1]:]
53 response = processor.tokenizer.decode(gen_ids, skip_special_tokens=True)
54 print(f"Response {idx+1}: {response}")1import torch
2from transformers import AutoModelForCausalLM, AutoProcessor, GenerationConfig
3
4processor = AutoProcessor.from_pretrained(
5 'jinaai/jina-vlm', use_fast=False, trust_remote_code=True
6)
7model = AutoModelForCausalLM.from_pretrained(
8 'jinaai/jina-vlm',
9 device_map='auto',
10 torch_dtype=torch.bfloat16,
11 attn_implementation='flash_attention_2',
12 trust_remote_code=True
13)
14
15images = [
16 ['https://picsum.photos/id/22/800/600'],
17 ['https://picsum.photos/id/49/800/600'],
18 ['https://picsum.photos/id/0/800/600', 'https://picsum.photos/id/2/800/600'],
19 [],
20]
21conversations = [
22 [
23 {
24 'role': 'user',
25 'content': [
26 {'type': 'image', 'image': images[0][0]},
27 {'type': 'text', 'text': 'What is the man doing in this image?'},
28 ],
29 }
30 ],
31 [
32 {
33 'role': 'user',
34 'content': [
35 {'type': 'image', 'image': images[1][0]},
36 {'type': 'text', 'text': 'What country\'s flag is in this image?'},
37 ],
38 }
39 ],
40 [
41 {
42 'role': 'user',
43 'content': [
44 {'type': 'image', 'image': images[2][0]},
45 {'type': 'image', 'image': images[2][1]},
46 {'type': 'text', 'text': 'What is the difference between these two images?'},
47 ],
48 }
49 ],
50 [
51 {
52 'role': 'user',
53 'content': [
54 {'type': 'text', 'text': 'Describe the concept of polymorphism in Computer Science'},
55 ],
56 }
57 ],
58]
59
60texts = processor.apply_chat_template(conversations, add_generation_prompt=True)
61inputs = processor(text=texts, images=images, padding='longest', return_tensors='pt')
62inputs = {k: v.to(model.device) if isinstance(v, torch.Tensor) else v for k, v in inputs.items()}
63
64output = model.generate(
65 **inputs,
66 generation_config=GenerationConfig(max_new_tokens=512, do_sample=False),
67 return_dict_in_generate=True,
68 use_model_defaults=True,
69)
70
71for idx in range(len(output.sequences)):
72 gen_ids = output.sequences[idx][inputs['input_ids'].shape[-1]:]
73 response = processor.tokenizer.decode(gen_ids, skip_special_tokens=True)
74 print(f"Response {idx+1}: {response}")1import torch
2from transformers import AutoModel, AutoProcessor
3
4processor = AutoProcessor.from_pretrained(
5 'jinaai/jina-vlm', use_fast=False, trust_remote_code=True
6)
7model = AutoModel.from_pretrained(
8 'jinaai/jina-vlm',
9 device_map='auto',
10 torch_dtype=torch.bfloat16,
11 attn_implementation='flash_attention_2',
12 trust_remote_code=True
13)
14
15images = [
16 ['https://picsum.photos/id/22/800/600'],
17 ['https://picsum.photos/id/49/800/600'],
18 ['https://picsum.photos/id/0/800/600', 'https://picsum.photos/id/2/800/600'],
19 [],
20]
21conversations = [
22 [
23 {
24 'role': 'user',
25 'content': [
26 {'type': 'image', 'image': images[0][0]},
27 {'type': 'text', 'text': 'What is the man doing in this image?'},
28 ],
29 }
30 ],
31 [
32 {
33 'role': 'user',
34 'content': [
35 {'type': 'image', 'image': images[1][0]},
36 {'type': 'text', 'text': 'What country\'s flag is in this image?'},
37 ],
38 }
39 ],
40 [
41 {
42 'role': 'user',
43 'content': [
44 {'type': 'image', 'image': images[2][0]},
45 {'type': 'image', 'image': images[2][1]},
46 {'type': 'text', 'text': 'What is the difference between these two images?'},
47 ],
48 }
49 ],
50 [
51 {
52 'role': 'user',
53 'content': [
54 {'type': 'text', 'text': 'Describe the concept of polymorphism in Computer Science'},
55 ],
56 }
57 ],
58]
59
60texts = processor.apply_chat_template(conversations, add_generation_prompt=True)
61inputs = processor(text=texts, images=images, padding='longest', return_tensors='pt')
62inputs = {k: v.to(model.device) if isinstance(v, torch.Tensor) else v for k, v in inputs.items()}
63
64output = model(**inputs)
65print(output)1from vllm import LLM, SamplingParams
2
3llm = LLM(
4 model='jinaai/jina-vlm',
5 runner='generate',
6 trust_remote_code=True,
7 dtype='bfloat16', # or float16, float32
8 hf_overrides={'_attn_implementation': 'flash_attention_2'},
9)
10conversations = [
11 [
12 {
13 'role': 'user',
14 'content': [
15 {
16 'type': 'image_url',
17 'image_url': {
18 'url': 'https://picsum.photos/800/600'
19 }
20 },
21 {'type': 'text', 'text': 'Describe this image'}
22 ],
23 }
24 ]
25]
26response = llm.chat(
27 messages=conversations,
28 add_generation_prompt=True,
29 chat_template_kwargs={
30 'always_start_with_space': True,
31 'image_prompt_token': '<|image|>',
32 },
33 sampling_params=SamplingParams(
34 temperature=0.0,
35 n=1,
36 max_tokens=64,
37 top_p=1.0,
38 repetition_penalty=1.0,
39 top_k=0,
40 ),
41)
42print([r.outputs[0].text for r in response])1from vllm import LLM, SamplingParams
2
3llm = LLM(
4 model='jinaai/jina-vlm',
5 runner='generate',
6 trust_remote_code=True,
7 dtype='bfloat16', # or float16, float32
8 hf_overrides={'_attn_implementation': 'flash_attention_2'},
9)
10conversations = [
11 [
12 {
13 'role': 'user',
14 'content': [
15 {
16 'type': 'image_url',
17 'image_url': {
18 'url': 'https://picsum.photos/id/1/800/600'
19 }
20 },
21 {
22 'type': 'image_url',
23 'image_url': {
24 'url': 'https://picsum.photos/id/2/800/600'
25 }
26 },
27 {'type': 'text', 'text': 'What is the difference between these images?'}
28 ],
29 }
30 ]
31]
32response = llm.chat(
33 messages=conversations,
34 add_generation_prompt=True,
35 chat_template_kwargs={
36 'always_start_with_space': True,
37 'image_prompt_token': '<|image|>',
38 },
39 sampling_params=SamplingParams(
40 temperature=0.0,
41 n=1,
42 max_tokens=64,
43 top_p=1.0,
44 repetition_penalty=1.0,
45 top_k=0,
46 ),
47)
48print([r.outputs[0].text for r in response])1from vllm import LLM, SamplingParams
2
3llm = LLM(
4 model='jinaai/jina-vlm',
5 runner='generate',
6 trust_remote_code=True,
7 dtype='bfloat16', # or float16, float32
8 hf_overrides={'_attn_implementation': 'flash_attention_2'},
9)
10conversations = [
11 [
12 {
13 'role': 'user',
14 'content': [
15 {'type': 'text', 'text': 'Explain quantum computing in simple terms'}
16 ],
17 }
18 ]
19]
20response = llm.chat(
21 messages=conversations,
22 add_generation_prompt=True,
23 chat_template_kwargs={
24 'always_start_with_space': True,
25 'image_prompt_token': '<|image|>',
26 },
27 sampling_params=SamplingParams(
28 temperature=0.0,
29 n=1,
30 max_tokens=64,
31 top_p=1.0,
32 repetition_penalty=1.0,
33 top_k=0,
34 ),
35)
36print([r.outputs[0].text for r in response])1from vllm import LLM, SamplingParams
2
3llm = LLM(
4 model='jinaai/jina-vlm',
5 runner='generate',
6 trust_remote_code=True,
7 dtype='bfloat16', # or float16, float32
8 hf_overrides={'_attn_implementation': 'flash_attention_2'},
9)
10conversations = [
11 [
12 {
13 'role': 'user',
14 'content': [
15 {
16 'type': 'image_url',
17 'image_url': {
18 'url': 'https://picsum.photos/id/22/800/600'
19 }
20 },
21 {'type': 'text', 'text': 'What is the man doing in this image?'}
22 ],
23 }
24 ],
25 [
26 {
27 'role': 'user',
28 'content': [
29 {
30 'type': 'image_url',
31 'image_url': {
32 'url': 'https://picsum.photos/id/49/800/600'
33 }
34 },
35 {'type': 'text', 'text': 'What country\'s flag is in this image?'}
36 ],
37 }
38 ]
39]
40response = llm.chat(
41 messages=conversations,
42 add_generation_prompt=True,
43 chat_template_kwargs={
44 'always_start_with_space': True,
45 'image_prompt_token': '<|image|>',
46 },
47 sampling_params=SamplingParams(
48 temperature=0.0,
49 n=1,
50 max_tokens=64,
51 top_p=1.0,
52 repetition_penalty=1.0,
53 top_k=0,
54 ),
55)
56print([r.outputs[0].text for r in response])1from vllm import LLM, SamplingParams
2
3llm = LLM(
4 model='jinaai/jina-vlm',
5 runner='generate',
6 trust_remote_code=True,
7 dtype='bfloat16', # or float16, float32
8 hf_overrides={'_attn_implementation': 'flash_attention_2'},
9)
10conversations = [
11 [
12 {
13 'role': 'user',
14 'content': [
15 {
16 'type': 'image_url',
17 'image_url': {
18 'url': 'https://picsum.photos/id/22/800/600'
19 }
20 },
21 {'type': 'text', 'text': 'What is the man doing in this image?'}
22 ],
23 }
24 ],
25 [
26 {
27 'role': 'user',
28 'content': [
29 {
30 'type': 'image_url',
31 'image_url': {
32 'url': 'https://picsum.photos/id/49/800/600'
33 }
34 },
35 {'type': 'text', 'text': 'What country\'s flag is in this image?'}
36 ],
37 }
38 ],
39 [
40 {
41 'role': 'user',
42 'content': [
43 {
44 'type': 'image_url',
45 'image_url': {
46 'url': 'https://picsum.photos/id/0/800/600'
47 }
48 },
49 {
50 'type': 'image_url',
51 'image_url': {
52 'url': 'https://picsum.photos/id/2/800/600'
53 }
54 },
55 {'type': 'text', 'text': 'What is the difference between these two images?'}
56 ],
57 }
58 ],
59 [
60 {
61 'role': 'user',
62 'content': [
63 {'type': 'text', 'text': 'Describe the concept of polymorphism in Computer Science'}
64 ],
65 }
66 ]
67]
68response = llm.chat(
69 messages=conversations,
70 add_generation_prompt=True,
71 chat_template_kwargs={
72 'always_start_with_space': True,
73 'image_prompt_token': '<|image|>',
74 },
75 sampling_params=SamplingParams(
76 temperature=0.0,
77 n=1,
78 max_tokens=64,
79 top_p=1.0,
80 repetition_penalty=1.0,
81 top_k=0,
82 ),
83)
84print([r.outputs[0].text for r in response])1from vllm import LLM, SamplingParams
2
3llm = LLM(
4 model='jinaai/jina-vlm',
5 runner='pooling',
6 trust_remote_code=True,
7 dtype='bfloat16', # or float16, float32
8 hf_overrides={'_attn_implementation': 'flash_attention_2'},
9)
10conversations = [
11 [
12 {
13 'role': 'user',
14 'content': [
15 {
16 'type': 'image_url',
17 'image_url': {
18 'url': 'https://picsum.photos/id/22/800/600'
19 }
20 },
21 {'type': 'text', 'text': 'What is the man doing in this image?'}
22 ],
23 }
24 ],
25 [
26 {
27 'role': 'user',
28 'content': [
29 {
30 'type': 'image_url',
31 'image_url': {
32 'url': 'https://picsum.photos/id/49/800/600'
33 }
34 },
35 {'type': 'text', 'text': 'What country\'s flag is in this image?'}
36 ],
37 }
38 ],
39 [
40 {
41 'role': 'user',
42 'content': [
43 {
44 'type': 'image_url',
45 'image_url': {
46 'url': 'https://picsum.photos/id/0/800/600'
47 }
48 },
49 {
50 'type': 'image_url',
51 'image_url': {
52 'url': 'https://picsum.photos/id/2/800/600'
53 }
54 },
55 {'type': 'text', 'text': 'What is the difference between these two images?'}
56 ],
57 }
58 ],
59 [
60 {
61 'role': 'user',
62 'content': [
63 {'type': 'text', 'text': 'Describe the concept of polymorphism in Computer Science'}
64 ],
65 }
66 ]
67]
68prompts = llm.preprocess_chat(
69 messages=conversations,
70 chat_template_kwargs={
71 'always_start_with_space': True,
72 'image_prompt_token': '<|image|>',
73 },
74)
75output = llm.encode(prompts, pooling_task='token_embed')
76print([out.outputs.data for out in output])| Model | MMMB ar | MMMB cn | MMMB en | MMMB avg | MMBench avg | Overall |
|---|---|---|---|---|---|---|
| jina-vlm | 76.9 | 80.0 | 82.0 | 78.8 | 74.3 | 59.6 |
| Qwen2-VL-2B | 68.3 | 74.2 | 78.3 | 71.3 | 69.4 | 53.8 |
| Qwen3-VL-2B | 72.7 | 75.7 | 80.7 | 75.0 | 72.3 | 58.2 |
| InternVL3-2B | 68.6 | 78.3 | 81.9 | 73.6 | 71.9 | 57.4 |
| InternVL3.5-2B | 68.5 | 77.7 | 80.2 | 74.6 | 70.9 | 58.0 |
| Model | AI2D | ChartQA | TextVQA | DocVQA | InfoVQA | OCRBench | SEED-2+ | CharXiv | Avg |
|---|---|---|---|---|---|---|---|---|---|
| jina-vlm | 82.0 | 81.9 | 83.2 | 90.6 | 71.6 | 778 | 67.2 | 32.3/63.5 | 72.3 |
| Qwen2-VL-2B | 74.7 | 73.5 | 79.7 | 89.2 | 64.0 | 809 | 62.4 | 23.3/55.0 | 66.4 |
| Qwen3-VL-2B | 76.9 | 77.2 | 79.5 | 92.3 | 71.9 | 858 | 67.3 | 28.8/62.3 | 71.6 |
| InternVL3-2B | 78.6 | 80.2 | 77.0 | 87.4 | 67.1 | 835 | 64.6 | 28.3/54.7 | 69.2 |
| InternVL3.5-2B | 78.8 | 80.7 | 76.5 | 88.5 | 69.3 | 836 | 68.0 | 31.6/65.0 | 71.6 |
| Model | MMLU | MMLU-Pro | GSM-8K | ARC-C | HellaSwag |
|---|---|---|---|---|---|
| jina-vlm | 56.1 | 30.3 | 71.3 | 77.3 | 59.4 |
| Qwen3-1.7B | 62.6 | 46.4 | 75.3 | 73.4 | 59.0 |
jina-vlm useful in your research, please cite our technical report:1@misc{koukounas2025jinavlm,
2 title={Jina-VLM: Small Multilingual Vision Language Model},
3 author={Andreas Koukounas and Georgios Mastrapas and Florian Hönicke and Sedigheh Eslami and Guillaume Roncari and Scott Martens and Han Xiao},
4 year={2025},
5 eprint={2512.04032},
6 archivePrefix={arXiv},
7 primaryClass={cs.CL},
8 url={https://arxiv.org/abs/2512.04032},
9}jina-vlm is licensed under CC BY-NC 4.0. For commercial usage inquiries, feel free to contact us.