jepa.cpp GGUF schema (v1)¶
One GGUF file is one model bundle: an encoder, and optionally a predictor and/or a head. The converter, the loader and the graph builder all implement exactly this document — change it here first. The graph these keys describe is in Architecture; the Python side that writes them is in Converter.
General metadata¶
| key | type | notes |
|---|---|---|
general.architecture |
str | always "jepa" |
general.name |
str | e.g. "vjepa2-vitl-fpc64-256" |
general.license |
str | "mit", "cc-by-nc-4.0", "apache-2.0" — copied from the source |
general.source_url |
str | HF repo or download URL |
general.file_type |
u32 | ggml ftype (0 = f32, 1 = f16, 7 = q8_0, ...) |
jepa.schema_version |
u32 | 1 |
jepa.family |
str | ijepa · vjepa · vjepa2 · vjepa2_1 · levjepa · hfvit (LeJEPA-style / LeWM encoder) · lewm |
jepa.modality |
str | image · video · image+video |
Encoder (jepa.enc.*)¶
| key | type | notes |
|---|---|---|
jepa.enc.embed_dim |
u32 | 1024 for ViT-L |
jepa.enc.n_layer |
u32 | |
jepa.enc.n_head |
u32 | V-JEPA 2 ViT-g = 22, I-JEPA ViT-g = 16 |
jepa.enc.ffn_dim |
u32 | explicit, not a ratio (6144 for g) |
jepa.enc.patch_size |
u32 | 14 or 16 |
jepa.enc.tubelet_size |
u32 | 1 for image models, 2 for video |
jepa.enc.img_size |
u32 | training crop (224 / 256 / 384); inference may differ if pos scheme allows |
jepa.enc.n_frames |
u32 | training frames (1 for image, 16 or 64 for video) |
jepa.enc.in_chans |
u32 | 3 |
jepa.enc.ln_eps |
f32 | 1e-6 (HF I-JEPA config says 1e-6 too) |
jepa.enc.act |
str | gelu_erf (all Meta JEPAs) · gelu_tanh · silu |
jepa.enc.pos_type |
str | sincos2d · sincos3d · learned · rope3d |
jepa.enc.rope_theta |
f32 | 10000 |
jepa.enc.rope_interpolate |
bool | V-JEPA 2.1 interpolate_rope. The "pretrained grid" it rescales h/w to is not img_size / patch_size: Meta hard-codes 256 / patch_size (16 for patch 16, 18 for patch 14) — the loader derives train_grid_h = train_grid_w = 256 / patch_size = jepa.enc.rope_ref_grid (see src/rope3d.h). family == vjepa2_1 also selects the interleaved cos/sin layout (variant = 1); vjepa2 uses the tiled HF layout (variant = 0). |
jepa.enc.rope_freq_layout |
str | tiled (V-JEPA 2: per-axis cos/sin table is [f_0..f_{d/2-1}, f_0..f_{d/2-1}], the Meta/HF quirk) · interleaved (V-JEPA 2.1: [f_0,f_0,f_1,f_1,...], textbook RoPE). See scripts/jepa_convert/VJEPA_NOTES.md |
jepa.enc.rope_ref_grid |
u32 | V-JEPA 2.1 only: spatial RoPE positions are rescaled h *= (ref_grid-1)/(gh-1) (16 for patch 16) |
jepa.enc.cls_token |
bool | true for hfvit (DINOv2-style) and levjepa (the only video family with one), false for Meta JEPAs |
jepa.enc.n_registers |
u32 | 0 |
jepa.enc.qkv_fused |
bool | true → attn_qkv.* tensor with rows [q;k;v]; false → separate q/k/v |
jepa.enc.modality_embed |
bool | V-JEPA 2.1 |
jepa.enc.image_patch_embed |
bool | V-JEPA 2.1 separate 1×16×16 image tokenizer |
jepa.enc.hier_layers |
[u32] | V-JEPA 2.1 hierarchical layer ids (per-layer norms exist for these) |
jepa.enc.layer_scale |
bool | DINOv2-style ls1/ls2 present |
jepa.enc.attn_mode |
str | full (every existing file: the key is absent and the loader defaults to it) · block_causal (levjepa: key j is visible to query i iff frame_id(i) >= frame_id(j), with the CLS row open and the CLS column closed — see the levjepa family note) |
jepa.enc.proj_act |
str | lewm only: activation of the enc.proj.* MLP (gelu_erf) |
Encoder tensors (ggml dim order is reversed from PyTorch; PyTorch row-major is stored as-is, i.e. ne[0] = last PyTorch dim):
enc.patch_embed.weight [embed_dim, in_chans * tubelet * patch * patch] (conv flattened to a matmul, C-T-H-W order)
enc.patch_embed.bias [embed_dim]
enc.patch_embed_img.weight [embed_dim, in_chans * 1 * patch * patch] (2.1 only)
enc.patch_embed_img.bias
enc.pos_embed [n_tokens, embed_dim] (sincos*/learned; rows = [CLS?; patches] — registers are appended AFTER the pos add and have no pos rows)
enc.cls_token [embed_dim]
enc.reg_tokens [n_registers, embed_dim]
enc.mod_embed_img [embed_dim] (2.1)
enc.mod_embed_video [embed_dim] (2.1)
enc.blk.{i}.ln1.weight / .bias
enc.blk.{i}.attn_qkv.weight [3*embed_dim, embed_dim] (+ .bias) -- or attn_q / attn_k / attn_v
enc.blk.{i}.attn_out.weight [embed_dim, embed_dim] (+ .bias)
enc.blk.{i}.ln2.weight / .bias
enc.blk.{i}.ffn_up.weight [ffn_dim, embed_dim] (+ .bias)
enc.blk.{i}.ffn_down.weight [embed_dim, ffn_dim] (+ .bias)
enc.blk.{i}.ls1 / .ls2 [embed_dim] (layer_scale only)
enc.norm.weight / .bias (final LN; for 2.1 this is norms_block[-1])
enc.hier_norm.{k}.weight/.bias (2.1: one per hier layer, k = index into hier_layers)
enc.proj.0.weight/.bias [proj_hidden, embed_dim] (lewm: projector MLP on the CLS token, BatchNorm folded)
enc.proj.2.weight/.bias [embed_dim, proj_hidden] (lewm; act between = jepa.enc.proj_act)
Sincos tables are precomputed at conversion for the training grid (enc.pos_embed); the runtime bicubic/trilinear-interpolates for other grids.
Predictor (jepa.pred.*) — optional¶
| key | notes |
|---|---|
jepa.pred.kind |
masked (I-JEPA / V-JEPA / V-JEPA 2 / 2.1) · ac (V-JEPA 2-AC) · lewm |
jepa.pred.embed_dim, n_layer, n_head, ffn_dim |
|
jepa.pred.n_mask_tokens |
10 (V-JEPA 2), 8 (2.1) |
jepa.pred.out_dim |
width of pred.proj output (enc_dim for V-JEPA 2; teacher_dim / n_hier_in = 1664 for 2.1 ViT-B) |
jepa.pred.rope_freq_layout, jepa.pred.rope_interpolate, jepa.pred.rope_ref_grid, jepa.pred.grid_size |
predictor RoPE convention (2.1: interleaved, not interpolated, mask ids decoded on the fixed img_size/patch grid = grid_size) |
jepa.pred.n_hier_in |
2.1: number of hierarchical encoder outputs concatenated into pred.embed (1 for ViT-B/L, 4 for g/G) |
jepa.pred.modality_embed, jepa.pred.context_proj |
2.1: pred.mod_embed_* / pred.proj_context.* present |
jepa.pred.action_dim, jepa.pred.state_dim |
7 / 7 for V-JEPA 2-AC; 10 / 0 for LeWM |
jepa.pred.frame_causal |
bool (AC) |
jepa.pred.n_frames |
LeWM: 3 |
jepa.pred.head_dim |
u32, only when n_head * head_dim != embed_dim (LeWM: 16 × 64 in a 192-d model) |
jepa.pred.ln_eps |
f32, eps of the affine LayerNorms in the predictor (LeWM: 1e-5) |
jepa.pred.adaln_eps |
f32, eps of the non-affine adaLN norms (LeWM: 1e-6) |
jepa.pred.act |
str, FFN activation (gelu_erf) |
jepa.pred.qkv_bias |
bool (LeWM: false → no attn_qkv.bias) |
jepa.pred.action_act |
str, activation inside the 2-layer pred.action_embed MLP (LeWM: silu) |
jepa.pred.proj_act |
str, activation inside the 2-layer pred.proj MLP (LeWM: gelu_erf) |
pred.embed.weight/bias [pred_dim, enc_dim] (context projection; single Linear when jepa.pred.n_hier_in == 1 (V-JEPA 2, 2.1 ViT-B/L); 2.1 with n_hier_in > 1 (ViT-g/G): 2-layer MLP pred.embed.0 / pred.embed.2 over the concatenated hier features)
pred.mask_tokens [n_mask_tokens, pred_dim]
pred.pos_embed [n_tokens, pred_dim] (sincos models only)
pred.blk.{i}.* same layout as enc.blk
pred.norm.weight/bias
pred.proj.weight/bias [enc_dim (or teacher_dim), pred_dim]
pred.action_embed.weight/bias [pred_dim, action_dim] (AC: single linear)
pred.action_embed.0 / .2 [4*pred_dim, action_dim] / [pred_dim, 4*pred_dim] (lewm: 2-layer MLP, act = jepa.pred.action_act)
pred.proj_context.weight/bias [teacher_dim, pred_dim] (2.1 return_all_tokens: projection of the *context* tokens)
pred.mod_embed_img / pred.mod_embed_video [pred_dim] (2.1: added to every predictor token)
pred.action_embed.weight/bias [pred_dim, action_dim]
pred.state_embed.weight/bias [pred_dim, state_dim]
pred.blk.{i}.adaln.weight/bias [6*pred_dim, pred_dim] (lewm: adaLN-zero modulation, see the lewm section)
pred.proj.0 / .2 [proj_hidden, pred_dim] / [out_dim, proj_hidden] (lewm: 2-layer MLP, BatchNorm folded, act = jepa.pred.proj_act)
Head (jepa.head.*) — optional¶
| key | notes |
|---|---|
jepa.head.kind |
attentive_pool (V-JEPA 2 classifiers) · linear_cls · none |
jepa.head.n_classes |
|
jepa.head.n_pool_layers |
3 |
jepa.head.labels |
[str] id2label, in order |
head.blk.{i}.* self-attention blocks over all tokens, run FIRST (same layout as enc.blk)
head.query [1, embed_dim] (raw query token = residual of the cross-attn)
head.xattn.ln_kv.weight/bias (HF layer_norm1: applied to keys/values only, NOT to the query)
head.xattn.q / .k / .v .weight [embed_dim, embed_dim] + .bias (no output projection in HF)
head.xattn.ln2.weight/bias (HF layer_norm2, on query + attn)
head.xattn.ffn_up / ffn_down MLP after the cross-attention (fc1/fc2)
head.cls.weight/bias [n_classes, embed_dim] (no final norm: classifier acts directly on the pooled token)
Preprocessing metadata (jepa.pre.*)¶
| key | notes |
|---|---|
jepa.pre.mean, jepa.pre.std |
[f32 ×3], ImageNet (0.485,0.456,0.406)/(0.229,0.224,0.225) for V-JEPA, LeJEPA, LeWM. The HF I-JEPA checkpoint ships 0.5/0.5/0.5 (ViTImageProcessor) and that is what the file carries — parity is against the HF processor |
jepa.pre.resize_short |
short-side resize before crop (V-JEPA: crop×256/224; LeJEPA: 256; LeWM: 224). For resize_mode=squash it is the target square size (I-JEPA HF: 224) |
jepa.pre.crop |
center crop size |
jepa.pre.resample |
bilinear · bicubic (must match the reference processor) |
jepa.pre.resize_mode |
optional — loader defaults to shortest_edge when absent (video-family files omit it). shortest_edge (resize short side to resize_short, keep aspect, centre-crop crop) · squash (resize directly to crop×crop, aspect not preserved — HF I-JEPA ViTImageProcessor with size={height,width}) |
jepa.pre.rescale |
f32, pixel scale before normalisation (1/255) |
Reference resampler. The HF 5.x processors (ViTImageProcessor, BitImageProcessor, VJEPA2VideoProcessor) are torchvision-backed: torchvision.transforms.v2.functional.resize on the uint8 CHW tensor with antialias=True, rounded back to uint8, then center-crop, /255, normalize. PIL resampling differs by up to 1 uint8 LSB on ~0.1–0.3 % of pixels; the C++ preprocessor targets the torchvision result (see tests/fixtures/README.md).
Token order¶
Video tokens are T-major, then H, then W (i = t*gh*gw + h*gw + w). Image tokens are H-major then W. hfvit and levjepa prepend CLS (and, for hfvit, registers); a prepended CLS carries no position information at all — no pos-embed row and no RoPE rotation — so the grid ids above are those of the patch tokens only, shifted by the prefix.
Quantization rules¶
Quantize only 2-D weight matrices of *.attn_qkv / attn_q / attn_k / attn_v / attn_out, *.ffn_up / ffn_down, pred.proj* (incl. pred.proj_context), pred.embed*, enc.proj.*, head.blk.*, head.xattn.{q,k,v}, head.cls. K-quants need ne[0] % 256 == 0; fall back per tensor to q8_0 (or the q4_0/q5_0 sibling) otherwise.
Everything else stays F32, and this is a requirement rather than a convention: norms, biases, layer scales, position tables, CLS / register / mask / modality tokens and pred.blk.*.adaln biases reach ggml as the right-hand side of an add, a mul or a concat, and those ops take an f32 operand and assert on anything else. The loader checks it and refuses a file that gets it wrong (docs/architecture.md "Robustness"). Patch embeddings and pred.action_embed.* are matmul weights and so may be F16, but the converter writes them F32 today.
Converter dtype rule for --ftype f16: the quantizable set above is written as F16, everything else as F32.
Family notes¶
ijepa (facebook/ijepa_vith14_1k)¶
- The HF checkpoint stores the position table as a parameter (
embeddings.position_embeddings, [1,256,1280]). It is a 2-D sincos table, but with the halves ordered[emb_w | emb_h]— bit-identical tosincos_2d(16, 16, 1280, w_first=True)inscripts/jepa_convert/common.py(max abs diff 0.0), while the[emb_h | emb_w]ordering ofvjepa2/src/models/utils/pos_embs.pydiffers by up to 2.0. The converter stores the checkpoint table verbatim inenc.pos_embed; the runtime interpolates it for other grids (as HF does). - No CLS token, no predictor in the HF release; pooled feature = mean over the 256 patch tokens after
enc.norm. general.license = cc-by-nc-4.0.
levjepa (galilai-group/LeVJEPA-VideoMix-Large)¶
ViT-L/16 video encoder (modeling_levjepa.py, shipped with the weights; the file is the reference
implementation and trust_remote_code=True loads it). Three things separate it from vjepa2, and the
schema says all three:
tubelet_size = 1withn_frames = 16, so a 16×224×224 clip is16·14·14 = 3136patch tokens in the usual T-major/H/W order.cls_token = true— the first video family with one.enc.cls_tokenis prepended after the patch embedding and receives no position information: there is noenc.pos_embed(RoPE model) and RoPE skips it (RoPEAttention.forwardrotatesq/k[..., 1:, :]), so the runtime's cos/sin tables get an identity row (cos 1, sin 0) at index 0. Total 3137 tokens. Pooled feature = CLS afterenc.norm, i.e.last_hidden_state[:, 0].attn_mode = "block_causal"— an additive attention mask, not an option: with full attention the same weights give CLS cosine 0.945 and a worst patch token of 0.834 against the reference (archery_f16). Key j is visible to query i iffframe_id(i) >= frame_id(j), whereframe_idof a patch token isid // (gh*gw); the CLS row is fully open (it reads the whole clip) and the CLS column is closed to every patch query (it is a read-only sink, so nothing routes last-frame information into a first-frame token). Density 53.1 % at 3137 tokens. Everything else followsvjepa2: 3-D RoPE θ 10000 in the tiled layout (rope_freq_layout = tiled;rotate_queries_or_keysuses Meta'srepeat(1,1,1,2)expansion — the interleaved 2.1 layout scores CLS cosine 0.841 on these weights), nointerpolate_rope, LN eps 1e-6, GELU(erf), fused qkv with bias, pre-LN, no layer-scale.
The checkpoint is the EMA encoder only: no predictor, no head, no projector. jepa.pre.* comes from the
model card (there is no preprocessor_config.json): ImageNet mean/std, short side → 224, centre crop 224,
bicubic, rescale 1/255. A still image is fed as a 16-frame clip by repeating the frame, which is what
the model card's ImageNet probe does. general.license = cc-by-nc-4.0.
hfvit (OK-AI/lejepa-vits16-pretrain-in1k)¶
DINOv2-style ViT-S/16 from Open-Knowledge-AI/lite_ssl ("ViTv2"): LN eps 1e-6, GELU(erf), qkv/proj/ffn biases,
learned pos_embed with the CLS slot at row 0, no registers, no LayerScale (init_values=null). The checkpoint's
backbone.cva_module_proj.* (loss-side DINOHead) is not converted. Pooled feature = CLS after enc.norm.
lewm (quentinll/lewm-pusht)¶
Encoder = HF ViTModel tiny/14 written in the hfvit layout (LN eps 1e-12, ViTConfig default). The world-model
state is emb = enc.proj(CLS) (2-layer MLP, BatchNorm folded, GELU). Predictor (jepa.pred.kind = lewm,
192-d, 6 layers, 16 heads × 64, ffn 2048, n_frames 3, action_dim 10, causal over frames):
a_t = action_embed.2( silu( action_embed.0( action_t ) ) ) # (T, 192) [Conv1d(k=1) folded into .0]
x = emb + pred.pos_embed[:T]
for each block i:
sh_a, sc_a, g_a, sh_m, sc_m, g_m = split6( adaln( silu(a) ) ) # each (T, 192)
h = LN(x, no affine, eps=adaln_eps) * (1 + sc_a) + sh_a
h = ln1(h) # affine, eps=ln_eps
q,k,v = attn_qkv(h) (no bias) ; causal softmax(q kᵀ / 8) v over the T frames, 16 heads × 64
x = x + g_a * attn_out(...)
h = LN(x, no affine, eps=adaln_eps) * (1 + sc_m) + sh_m
x = x + g_m * ffn_down( gelu( ffn_up( ln2(h) ) ) )
x = pred.norm(x)
pred_t = pred.proj.2( gelu( pred.proj.0( x_t ) ) ) # next-state embedding in the same space as emb
pred_{T-1} as the next emb, slide a window of n_frames. Tensor names: enc.proj.{0,2},
pred.pos_embed, pred.blk.{i}.{adaln,ln1,attn_qkv,attn_out,ln2,ffn_up,ffn_down}, pred.norm,
pred.action_embed.{0,2}, pred.proj.{0,2}. Preprocessing: ImageNet mean/std, bilinear resize to 224 (square renders).
scripts/jepa_convert/selftest.py::lewm_predictor_forward is the executable reference of this graph.