[Frozen Vision Encoder] ──► [Trainable Projector] ──► [Qwen-0.5B + LoRA]
│ │ │
CLIP / ViT / I-JEPA LayerNorm+MLP rank=16, q_proj+v_proj
├── clip/
│ ├── projector.pt # Trained MLP bridge weights
│ ├── lora_adapter/ # LoRA adapter (loadable via peft)
│ ├── loss_history.json # Per-step training loss
│ └── config.json # Full experiment config
├── vit/
│ └── ... (same structure)
└── ijepa/
└── ... (same structure)
1 import json , torch
2 from PIL import Image
3 from peft import PeftModel
4 from transformers import AutoModelForCausalLM , AutoTokenizer , AutoConfig
5 from huggingface_hub import hf_hub_download , snapshot_download
6
7 REPO = "Teen-Different/CLIP-ViT-IJEPA-VLMs-0.5B"
8 ENCODER = "clip" # or "vit" or "ijepa"
9
10 # Download weights
11 cfg = json . load ( open ( hf_hub_download ( REPO , f" { ENCODER } /config.json" ) ) )
12 proj_path = hf_hub_download ( REPO , f" { ENCODER } /projector.pt" )
13 lora_dir = snapshot_download ( REPO , allow_patterns = f" { ENCODER } /lora_adapter/*" )
14 lora_path = f" { lora_dir } / { ENCODER } /lora_adapter"
15
16 # Load encoder (example for CLIP)
17 from transformers import CLIPVisionModel , CLIPImageProcessor
18 encoder = CLIPVisionModel . from_pretrained ( "openai/clip-vit-large-patch14" ,
19 torch_dtype = torch . bfloat16 ) . cuda ( ) . eval ( )
20 processor = CLIPImageProcessor . from_pretrained ( "openai/clip-vit-large-patch14" )
21
22 # Load projector
23 import torch . nn as nn
24 class Projector ( nn . Module ) :
25 def __init__ ( self , vd , ld ) :
26 super ( ) . __init__ ( )
27 self . norm = nn . LayerNorm ( vd )
28 self . fc1 = nn . Linear ( vd , ld )
29 self . act = nn . GELU ( )
30 self . fc2 = nn . Linear ( ld , ld )
31 def forward ( self , x ) :
32 return self . fc2 ( self . act ( self . fc1 ( self . norm ( x ) ) ) )
33
34 llm_dim = AutoConfig . from_pretrained ( cfg [ "llm_model_id" ] ) . hidden_size
35 projector = Projector ( 1024 , llm_dim ) . to ( torch . bfloat16 ) . cuda ( )
36 projector . load_state_dict ( torch . load ( proj_path , map_location = "cuda" , weights_only = True ) )
37 projector . eval ( )
38
39 # Load LLM + LoRA
40 tokenizer = AutoTokenizer . from_pretrained ( cfg [ "llm_model_id" ] )
41 llm = AutoModelForCausalLM . from_pretrained ( cfg [ "llm_model_id" ] ,
42 torch_dtype = torch . bfloat16 ) . cuda ( )
43 llm = PeftModel . from_pretrained ( llm , lora_path ) . cuda ( ) . eval ( )
44
45 # Run inference
46 image = Image . open ( "photo.jpg" ) . convert ( "RGB" )
47 pixels = processor ( images = [ image ] , return_tensors = "pt" ) [ "pixel_values" ] . cuda ( )
48
49 with torch . no_grad ( ) :
50 vis = encoder ( pixel_values = pixels ) . last_hidden_state [ : , 1 : , : ] # drop CLS
51 vis = projector ( vis )
52 tok = tokenizer ( "<image>\nWhat is in this image?" , return_tensors = "pt" ) . to ( "cuda" )
53 txt = llm . model . model . embed_tokens ( tok [ "input_ids" ] )
54 embeds = torch . cat ( [ vis , txt ] , dim = 1 )
55 mask = torch . ones ( embeds . shape [ : 2 ] , dtype = torch . long , device = "cuda" )
56 out = llm . generate ( inputs_embeds = embeds , attention_mask = mask ,
57 max_new_tokens = 50 , min_new_tokens = 3 , do_sample = False )
58 print ( tokenizer . decode ( out [ 0 ] , skip_special_tokens = True ) )
1 git clone https://github.com/REDDITARUN/CLIP-ViT-IJEPA-VLM.git && cd CLIP-ViT-IJEPA-VLM
2 pip install -r requirements.txt
3
4 # Inference
5 python inference.py --encoder clip --image photo.jpg --from-hub Teen-Different/CLIP-ViT-IJEPA-VLMs-0.5B
6
7 # Evaluate
8 python eval.py --from-hub Teen-Different/CLIP-ViT-IJEPA-VLMs-0.5B
9
10 # Interactive
11 python inference.py --encoder clip --image photo.jpg --from-hub Teen-Different/CLIP-ViT-IJEPA-VLMs-0.5B --interactive
1 @misc{clip-vit-ijepa-vlm-2026,
2 title={CLIP vs ViT vs I-JEPA: Vision Encoder Stitching Comparison},
3 author={Tarun Reddi},
4 year={2026},
5 url={https://github.com/REDDITARUN/CLIP-ViT-IJEPA-VLM}
6 }