package autogen import ( "encoding/binary" "encoding/json" "os" "path/filepath" "strings" "testing" ) // writeSafetensors lays down a real .safetensors container carrying only the // header (8-byte LE length + JSON table). Classification never reads past it, so // a synthetic file with no payload exercises the same path as a 10 GB encoder. func writeSafetensors(t *testing.T, path string, shapes map[string][]int64) { t.Helper() tbl := map[string]stTensor{} for k, v := range shapes { tbl[k] = stTensor{DType: "F16", Shape: v} } j, err := json.Marshal(tbl) if err != nil { t.Fatal(err) } if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } f, err := os.Create(path) if err != nil { t.Fatal(err) } defer f.Close() if err := binary.Write(f, binary.LittleEndian, uint64(len(j))); err != nil { t.Fatal(err) } if _, err := f.Write(j); err != nil { t.Fatal(err) } } func TestAutogen_classifySafetensors(t *testing.T) { cases := []struct { name string shapes map[string][]int64 wantRole ComponentRole wantFamily string wantWidth int64 wantVision bool }{ { name: "sd vae", shapes: map[string][]int64{"decoder.conv_in.weight": {512, 4, 3, 3}}, wantRole: RoleVae, wantFamily: VaeFamilySD, }, { name: "flux ae", shapes: map[string][]int64{"decoder.conv_in.weight": {512, 16, 3, 3}}, wantRole: RoleVae, wantFamily: VaeFamilyFlux, }, { name: "flux2 ae", shapes: map[string][]int64{"decoder.conv_in.weight": {512, 32, 3, 3}}, wantRole: RoleVae, wantFamily: VaeFamilyFlux2, }, { name: "wan 3d causal vae", shapes: map[string][]int64{"conv1.weight": {96, 3, 3, 3, 3}}, wantRole: RoleVae, wantFamily: VaeFamilyWan3D, }, { // Wan 2.1 and the Qwen-Image 20B VAE: 16-channel latent, and the // decoder projection is what states it. name: "wan 3d causal vae, 16-channel latent", shapes: map[string][]int64{ "conv1.weight": {32, 32, 1, 1, 1}, "decoder.conv1.weight": {384, 16, 3, 3, 3}, }, wantRole: RoleVae, wantFamily: VaeFamilyWan3D, wantWidth: 16, }, { // Qwen-Image 2.1's RGBA autoencoder: same family, 64-channel latent. name: "qwen-image 2.1 rgba vae", shapes: map[string][]int64{ "conv1.weight": {128, 128, 1, 1, 1}, "decoder.conv1.weight": {1152, 64, 1, 3, 3}, "encoder.conv1.weight": {96, 4, 1, 3, 3}, }, wantRole: RoleVae, wantFamily: VaeFamilyWan3D, wantWidth: 64, }, { name: "clip-l", shapes: map[string][]int64{"text_model.embeddings.token_embedding.weight": {49408, 768}}, wantRole: RoleClip, wantWidth: 768, }, { name: "clip-g", shapes: map[string][]int64{"text_model.embeddings.token_embedding.weight": {49408, 1280}}, wantRole: RoleClip, wantWidth: 1280, }, { name: "t5xxl", shapes: map[string][]int64{"encoder.block.0.layer.0.SelfAttention.q.weight": {4096, 4096}}, wantRole: RoleT5, wantWidth: 4096, }, { name: "plain decoder llm", shapes: map[string][]int64{"model.embed_tokens.weight": {131072, 3072}}, wantRole: RoleLlm, wantWidth: 3072, }, { name: "vision llm", shapes: map[string][]int64{ "model.embed_tokens.weight": {152064, 3584}, "visual.patch_embed.proj.weight": {1280, 3, 2, 14, 14}, }, wantRole: RoleLlm, wantWidth: 3584, wantVision: true, }, { // A DiT is not a component: it must not land in the pool, or the // diffusion model would be offered as its own text encoder. name: "dit is not a component", shapes: map[string][]int64{"double_blocks.0.img_attn.qkv.weight": {9216, 3072}}, wantRole: RoleNone, }, { // An unknown latent width is a VAE we cannot vouch for; wiring it // would produce a broken decode, so it is dropped entirely. name: "unknown latent width dropped", shapes: map[string][]int64{"decoder.conv_in.weight": {512, 7, 3, 3}}, wantRole: RoleNone, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { tbl := map[string]stTensor{} for k, v := range tc.shapes { tbl[k] = stTensor{Shape: v} } got := classifySafetensors(tbl, 1.0, "x.safetensors") if got.Role != tc.wantRole { t.Fatalf("role = %q, want %q", got.Role, tc.wantRole) } if got.Family != tc.wantFamily { t.Errorf("family = %q, want %q", got.Family, tc.wantFamily) } if got.Width != tc.wantWidth { t.Errorf("width = %d, want %d", got.Width, tc.wantWidth) } if got.Vision != tc.wantVision { t.Errorf("vision = %v, want %v", got.Vision, tc.wantVision) } }) } } func TestAutogen_ScanEncoderPool(t *testing.T) { root := t.TempDir() writeSafetensors(t, filepath.Join(root, "vae", "ae.safetensors"), map[string][]int64{"decoder.conv_in.weight": {512, 16, 3, 3}}) writeSafetensors(t, filepath.Join(root, "vae", "sdxl_vae.safetensors"), map[string][]int64{"decoder.conv_in.weight": {512, 4, 3, 3}}) writeSafetensors(t, filepath.Join(root, "clip", "clip_l.safetensors"), map[string][]int64{"text_model.embeddings.token_embedding.weight": {49408, 768}}) writeSafetensors(t, filepath.Join(root, "clip", "clip_g.safetensors"), map[string][]int64{"text_model.embeddings.token_embedding.weight": {49408, 1280}}) writeSafetensors(t, filepath.Join(root, "junk", "readme.safetensors"), map[string][]int64{"nothing.weight": {1, 1}}) // Not a safetensors container at all: must be skipped, not fatal. if err := os.WriteFile(filepath.Join(root, "junk", "half.safetensors"), []byte("no"), 0o644); err != nil { t.Fatal(err) } p := ScanEncoderPool([]string{root}) if len(p.Files) != 4 { t.Fatalf("files = %d, want 4: %+v", len(p.Files), p.Files) } if got := p.Vae(VaeFamilyFlux); !strings.HasSuffix(got, "ae.safetensors") { t.Errorf("flux vae = %q", got) } if got := p.Vae(VaeFamilySD, "sdxl"); !strings.Contains(got, "sdxl") { t.Errorf("sdxl vae = %q", got) } if got := p.Clip(768); !strings.Contains(got, "clip_l") { t.Errorf("clip-l = %q", got) } if got := p.Clip(1280); !strings.Contains(got, "clip_g") { t.Errorf("clip-g = %q", got) } if got := p.T5(); got != "" { t.Errorf("t5 = %q, want none", got) } // A hint that matches nothing still yields a stable pick, never a blank. if got := p.Vae(VaeFamilyFlux, "no-such-thing"); got == "" { t.Error("hint miss should fall back to the sorted-first candidate") } } func TestAutogen_EncoderPoolLlm(t *testing.T) { p := &EncoderPool{Files: []ComponentFile{ {Path: "/m/qwen25vl-q4.gguf", Role: RoleLlm, Width: 3584, SizeGB: 4, Mmproj: "/m/mmproj.gguf", Vision: true}, {Path: "/m/qwen25vl-q8.gguf", Role: RoleLlm, Width: 3584, SizeGB: 8, Mmproj: "/m/mmproj.gguf", Vision: true}, {Path: "/m/qwen25-7b.gguf", Role: RoleLlm, Width: 3584, SizeGB: 9}, {Path: "/m/qwen3-4b.gguf", Role: RoleLlm, Width: 2560, SizeGB: 3}, {Path: "/m/t5.gguf", Role: RoleT5, Width: 4096, SizeGB: 5}, }} // Widest file wins at equal width... if got, _ := p.Llm(3584, false, ""); got != "/m/qwen25-7b.gguf" { t.Errorf("llm(3584) = %q, want the largest", got) } // ...unless a vision tower is required, which excludes the bigger text-only // file and drags the paired projector along. got, proj := p.Llm(3584, true, "") if got != "/m/qwen25vl-q8.gguf" || proj != "/m/mmproj.gguf" { t.Errorf("llm(3584, vision) = %q/%q", got, proj) } if got, _ := p.Llm(2560, false, ""); got != "/m/qwen3-4b.gguf" { t.Errorf("llm(2560) = %q", got) } // An unmatched width picks nothing rather than the closest: a mismatched // encoder does not degrade, it fails to load. if got, _ := p.Llm(4096, false, ""); got != "" { t.Errorf("llm(4096) = %q, want none (t5 is not an llm)", got) } if got, _ := p.Llm(0, false, ""); got != "" { t.Errorf("llm(0) = %q, want none", got) } var nilPool *EncoderPool if got, _ := nilPool.Llm(3584, true, ""); got != "" { t.Error("nil pool must be inert") } if nilPool.Vae(VaeFamilyFlux) != "" || nilPool.Clip(768) != "" || nilPool.T5() != "" { t.Error("nil pool must be inert for every getter") } } // The real-world tie this pool has to break: Qwen3-4B-Instruct-2507 and // Qwen3-VL-4B-Instruct are both 2560 wide, and the Q8 quants of each round to // the same size, so the scan's own tiebreak (path) lands on the VL file. Only // the declared pin says which of them a given DiT was trained against. func TestAutogen_EncoderPoolLlmPrefer(t *testing.T) { const ( vl = "/m/QwenVL/Qwen3VL-4B-Instruct-Q8_0.gguf" q3q8 = "/m/lmstudio-community/Qwen3-4B-Instruct-2507-GGUF/Qwen3-4B-Instruct-2507-Q8_0.gguf" q3q4 = "/m/lmstudio-community/Qwen3-4B-Instruct-2507-GGUF/Qwen3-4B-Instruct-2507-Q4_K_M.gguf" ) p := &EncoderPool{Files: []ComponentFile{ {Path: vl, Role: RoleLlm, Width: 2560, SizeGB: 3.99, Vision: true, Mmproj: "/m/QwenVL/mmproj-Qwen3VL-4B-Instruct-F16.gguf"}, {Path: q3q8, Role: RoleLlm, Width: 2560, SizeGB: 3.99}, {Path: q3q4, Role: RoleLlm, Width: 2560, SizeGB: 2.32}, }} // No declaration: the path tiebreak takes the VL file, which is how // Z-Image ended up conditioned on Qwen3-VL. if got, _ := p.Llm(2560, false, ""); got != vl { t.Errorf("unpinned llm = %q, want %q", got, vl) } // A declared file of the matching width wins outright, even when the scan // finds a larger candidate. if got, _ := p.Llm(2560, false, q3q4); got != q3q4 { t.Errorf("pinned llm = %q, want %q", got, q3q4) } // A pin without a vision tower cannot serve a vision model (the caller then // retries text-only and gets it), and an unseen pin changes nothing. if got, _ := p.Llm(2560, true, q3q4); got != vl { t.Errorf("vision call took a text-only pin: %q", got) } if got, proj := p.Llm(2560, true, vl); got != vl || proj == "" { t.Errorf("vision pin = %q/%q", got, proj) } if got, _ := p.Llm(2560, false, "/m/elsewhere/qwen3-4b.gguf"); got != vl { t.Errorf("unseen pin = %q, want %q", got, vl) } } // onDisk moves the test into a fresh directory holding an empty file for each // relative name, so a declared component path like "ae.safetensors" exists: // fillEncoderSet clears declared files that are gone, and a fixture naming a // file that is not there would read as a model missing that part. func onDisk(t *testing.T, names ...string) { t.Helper() t.Chdir(t.TempDir()) for _, n := range names { if err := os.WriteFile(n, nil, 0o644); err != nil { t.Fatal(err) } } } func TestAutogen_fillEncoderSet(t *testing.T) { p := &EncoderPool{Files: []ComponentFile{ {Path: "/m/ae.safetensors", Role: RoleVae, Family: VaeFamilyFlux}, {Path: "/m/flux2-ae.safetensors", Role: RoleVae, Family: VaeFamilyFlux2}, {Path: "/m/clip_l.safetensors", Role: RoleClip, Width: 768}, {Path: "/m/t5xxl.gguf", Role: RoleT5, Width: 4096}, }} // A declared file outside the scan still wins, as long as it exists. hand := filepath.Join(t.TempDir(), "clip_l.safetensors") if err := os.WriteFile(hand, []byte("x"), 0o644); err != nil { t.Fatal(err) } got := fillEncoderSet(EncoderSet{ClipL: hand}, p) if got.ClipL != hand { t.Errorf("declared ClipL was overwritten: %q", got.ClipL) } // A declared file that is gone is cleared, so the pool fills the slot // (the flux2 VAE moved out of the models folder must not read as present). gone := fillEncoderSet(EncoderSet{Flux2Vae: filepath.Join(t.TempDir(), "moved", "ae.safetensors")}, p) if gone.Flux2Vae != "/m/flux2-ae.safetensors" { t.Errorf("missing declared flux2Vae = %q, want the pool's", gone.Flux2Vae) } // ...and a declared path the pool lists is kept without touching the disk. if kept := fillEncoderSet(EncoderSet{T5: "/m/t5xxl.gguf"}, p); kept.T5 != "/m/t5xxl.gguf" { t.Errorf("pool-listed declared t5 = %q", kept.T5) } if got.FluxVae != "/m/ae.safetensors" || got.Flux2Vae != "/m/flux2-ae.safetensors" { t.Errorf("vae fill = %q / %q", got.FluxVae, got.Flux2Vae) } // Z-Image ships an AE byte-identical to flux.1's, so it falls back to it. if got.ZimageVae != "/m/ae.safetensors" { t.Errorf("zimage vae = %q", got.ZimageVae) } if got.T5 != "/m/t5xxl.gguf" { t.Errorf("t5 = %q", got.T5) } if got.SdxlVae != "" { t.Errorf("sdxl vae = %q, want blank (none on disk)", got.SdxlVae) } // A nil pool must leave a hand-written set exactly as it was. in := EncoderSet{FluxVae: "a", ClipL: "b"} if fillEncoderSet(in, nil) != in { t.Error("nil pool must not alter the declared set") } } func TestAutogen_wantsVisionEncoder(t *testing.T) { cases := []struct { name string ov *Override want bool }{ {name: "LongCat-Image-Edit-Turbo-Q8_0", want: true}, {name: "Qwen-Rapid-NSFW", want: true}, {name: "flux1-kontext-dev", want: true}, {name: "sd15-inpaint", want: true}, {name: "LongCat-Image-Q8_0", want: false}, {name: "Z-Image-Turbo", want: false}, // "edit" only counts as its own token: "credit" is not an edit model. {name: "credit-model", want: false}, {name: "LongCat-Image-Edit", ov: &Override{LlmVision: "off"}, want: false}, {name: "LongCat-Image", ov: &Override{LlmVision: "on"}, want: true}, // Turning on reference editing asks for the projector too: without it // sd.cpp refuses the prompt on 2.1 rather than degrading. {name: "some-unified-editor", ov: &Override{RefEdit: "on"}, want: true}, // ...but refEdit off does not mean no vision: an inpaint model wants the // masked img2img route AND the projector. {name: "sd15-inpaint", ov: &Override{RefEdit: "off"}, want: true}, // An explicit vision off still wins over the refEdit implication. {name: "some-unified-editor", ov: &Override{RefEdit: "on", LlmVision: "off"}, want: false}, // Qwen-Image 2.1: one checkpoint does generation AND reference editing, // so nothing in the name says "edit" and the version is the only tell. {name: "qwen_image_2.1-q8_0", want: true}, {name: "Qwen-Image-2.1-Q4_K", want: true}, // The 20B line is a separate text-only checkpoint; its edit sibling is // named for it, so 2.1 must not widen the match to every qwen image. {name: "qwen_image-q8_0", want: false}, {name: "LongCat-Image", ov: &Override{LlmVision: "on"}, want: true}, } for _, tc := range cases { if got := wantsVisionEncoder("flux", tc.name, tc.ov); got != tc.want { t.Errorf("wantsVisionEncoder(%q) = %v, want %v", tc.name, got, tc.want) } } } func TestAutogen_condHiddenFrom(t *testing.T) { if got := condHiddenFrom(nil); got != 0 { t.Errorf("empty = %d, want 0", got) } if got := condHiddenFrom(map[string]int64{"txt_in.weight": 3584}); got != 3584 { t.Errorf("txt_in = %d", got) } // Highest-ranked present tensor wins when a model carries several. dims := map[string]int64{ "context_embedder.weight": 4096, "txt_in.weight": 3584, } if got := condHiddenFrom(dims); got != 3584 { t.Errorf("ranked pick = %d, want 3584", got) } if got := condHiddenFrom(map[string]int64{"unrelated.weight": 99}); got != 0 { t.Errorf("unrelated = %d, want 0", got) } // Qwen-Image 2.1 projects the caption through a two-layer MLP, so the width // is on txt_in.in_layer and the bare txt_in the 20B line uses is absent. // Missing this entry leaves the model with no stated encoder width at all. if got := condHiddenFrom(map[string]int64{"txt_in.in_layer.weight": 4096}); got != 4096 { t.Errorf("txt_in.in_layer = %d, want 4096", got) } } func TestAutogen_wan3dLatent(t *testing.T) { cases := []struct { arch string condHidden int64 want int64 }{ {"qwen_image", 4096, 64}, // Qwen-Image 2.1 + RGBA VAE {"qwen_image", 3584, 16}, // Qwen-Image 20B / Qwen-Image-Edit {"qwen_image", 2560, 16}, // Krea2 reports arch qwen_image too {"wan", 0, 0}, // Wan video: no opinion, 2.2 TI2V is 48-channel {"", 4096, 0}, } for _, c := range cases { if got := wan3dLatent(c.arch, c.condHidden); got != c.want { t.Errorf("wan3dLatent(%q, %d) = %d, want %d", c.arch, c.condHidden, got, c.want) } } } func TestAutogen_EncoderPoolVaeOfWidth(t *testing.T) { // Both files are family wan3d and both match the "qwen_image" hint, so // before latent width was recorded the pick was decided by sorted path, // which puts the 2.1 VAE ahead of the one the 20B line needs. pool := &EncoderPool{Files: []ComponentFile{ {Role: RoleVae, Family: VaeFamilyWan3D, Width: 64, Path: "m/qwen_image_2.1_vae_bf16.safetensors"}, {Role: RoleVae, Family: VaeFamilyWan3D, Width: 16, Path: "m/qwen_image_vae.safetensors"}, }} if got := pool.VaeOfWidth(VaeFamilyWan3D, 16, "qwen_image"); !strings.HasSuffix(got, "qwen_image_vae.safetensors") { t.Errorf("want 16 = %q", got) } if got := pool.VaeOfWidth(VaeFamilyWan3D, 64, "qwen_image"); !strings.HasSuffix(got, "qwen_image_2.1_vae_bf16.safetensors") { t.Errorf("want 64 = %q", got) } // want 0 keeps the old hint-then-sort behaviour. if got := pool.Vae(VaeFamilyWan3D, "qwen_image"); !strings.HasSuffix(got, "qwen_image_2.1_vae_bf16.safetensors") { t.Errorf("no opinion = %q", got) } // A width nothing carries falls back to the full list rather than to "", // so a file that predates Width being recorded never drops the --vae flag. if got := pool.VaeOfWidth(VaeFamilyWan3D, 48, "qwen_image"); got == "" { t.Error("unmatched width returned empty, want fallback") } } func TestAutogen_IsReferenceEditModel(t *testing.T) { cases := []struct { name string ov *Override want bool }{ {name: "LongCat-Image-Edit-Turbo-Q8_0", want: true}, {name: "Qwen-Rapid-NSFW", want: true}, {name: "flux1-kontext-dev", want: true}, {name: "LongCat-Image-Q8_0", want: false}, {name: "Z-Image-Turbo", want: false}, {name: "credit-model", want: false}, // Narrower than wantsVisionEncoder on purpose: an inpaint model is an // edit, but it wants the MASKED img2img route. Sending its source as a // reference would redraw the whole frame and ignore the mask. {name: "sd15-inpaint", want: false}, {name: "flux1-fill-dev", want: false}, {name: "LongCat-Image-Edit", ov: &Override{LlmVision: "off"}, want: false}, {name: "LongCat-Image", ov: &Override{LlmVision: "on"}, want: true}, // Unified checkpoint: edit-capable with no edit token in the name. {name: "qwen_image_2.1-q8_0", want: true}, {name: "Qwen-Image-2.1-Q4_K", want: true}, {name: "qwen_image-q8_0", want: false}, // An explicit off still wins over the version match. {name: "qwen_image_2.1-q8_0", ov: &Override{RefEdit: "off"}, want: false}, // FLUX.2 / FLUX.3: one checkpoint generates and edits, so no edit token. {name: "flux-3-image-klein-UD-Q8_K_XL", want: true}, {name: "flux-2-klein-4b-Q8_0", want: true}, {name: "flux2-dev-Q4_K_M", want: true}, {name: "FLUX.2-klein-9B", want: true}, {name: "flux-3-image-klein-Q8_0", ov: &Override{RefEdit: "off"}, want: false}, // flux.1 base is text-only, and "flux-1" must not read as "flux". {name: "flux1-dev-Q8_0", want: false}, {name: "flux-1-schnell", want: false}, {name: "fluxed-23-merge", want: false}, } for _, tc := range cases { if got := IsReferenceEditModel(tc.name, tc.ov); got != tc.want { t.Errorf("IsReferenceEditModel(%q) = %v, want %v", tc.name, got, tc.want) } } } // Flux.2 states a caption width that is a MULTIPLE of its encoder's hidden size // (klein 9B: txt_in 12288 against a 4096-wide Qwen3-8B), so the pick has to // divide rather than compare. Exactness still outranks concatenation, and the // factor cap keeps the rule from matching arbitrarily narrow encoders. func TestAutogen_EncoderPoolLlmConcat(t *testing.T) { p := &EncoderPool{Files: []ComponentFile{ {Path: "/m/qwen3-8b.gguf", Role: RoleLlm, Family: "qwen3", Width: 4096, SizeGB: 8}, {Path: "/m/qwen3-4b.gguf", Role: RoleLlm, Family: "qwen3", Width: 2560, SizeGB: 3}, {Path: "/m/tiny-1024.gguf", Role: RoleLlm, Family: "qwen3", Width: 1024, SizeGB: 1}, }} // klein 9B: 12288 = 3 x 4096. if got, _ := p.Llm(12288, false, ""); got != "/m/qwen3-8b.gguf" { t.Errorf("llm(12288) = %q, want the 4096-wide encoder", got) } // The global pin is 2560 wide: 12288 is not a multiple of it, so it must not // be dragged in. This is the exact shape of the klein-9B mis-wire. if got, _ := p.Llm(12288, false, "/m/qwen3-4b.gguf"); got != "/m/qwen3-8b.gguf" { t.Errorf("llm(12288, pin=4b) = %q, want the pin ignored", got) } // 12288/1024 = 12, not a known caption layout: a narrow encoder is not a candidate // just because the arithmetic works. narrow := &EncoderPool{Files: []ComponentFile{ {Path: "/m/tiny-1024.gguf", Role: RoleLlm, Family: "qwen3", Width: 1024, SizeGB: 1}, }} if got, _ := narrow.Llm(12288, false, ""); got != "" { t.Errorf("llm(12288) over a 1024-wide pool = %q, want none", got) } // An exact match beats a concatenated one even when the concat candidate is // bigger and would win the size tiebreak within a tier. exact := &EncoderPool{Files: []ComponentFile{ {Path: "/m/wide-12288.gguf", Role: RoleLlm, Family: "qwen3", Width: 12288, SizeGB: 2}, {Path: "/m/qwen3-8b.gguf", Role: RoleLlm, Family: "qwen3", Width: 4096, SizeGB: 8}, }} if got, _ := exact.Llm(12288, false, ""); got != "/m/wide-12288.gguf" { t.Errorf("llm(12288) = %q, want the exact-width file", got) } // A pin only reachable by concat does not outrank an exact-width scan hit. if got, _ := exact.Llm(12288, false, "/m/qwen3-8b.gguf"); got != "/m/wide-12288.gguf" { t.Errorf("llm(12288, pin=concat) = %q, want the exact-width file", got) } // ...but a pin at the same tier still wins outright. if got, _ := p.Llm(12288, false, "/m/qwen3-8b.gguf"); got != "/m/qwen3-8b.gguf" { t.Errorf("llm(12288, pin=8b) = %q, want the pin", got) } } func TestAutogen_captionFactor(t *testing.T) { cases := []struct { hidden, width, want int64 }{ {2560, 2560, 1}, // the ordinary case: one encoder hidden state {12288, 4096, 3}, // flux.2 klein 9B {7680, 2560, 3}, // flux.2 klein 4B {15360, 5120, 3}, // flux.2 dev (Mistral-Small-3) {12288, 2560, 0}, // not divisible {12288, 1024, 0}, // divisible but past the concat cap {4096, 0, 0}, // unmeasured encoder {0, 4096, 0}, // unmeasured DiT {-1, 4096, 0}, // defensive } for _, c := range cases { if got := captionFactor(c.hidden, c.width); got != c.want { t.Errorf("captionFactor(%d, %d) = %d, want %d", c.hidden, c.width, got, c.want) } } } func TestAutogen_knowsLlm(t *testing.T) { p := &EncoderPool{Files: []ComponentFile{ {Path: "/m/qwen3-4b.gguf", Role: RoleLlm, Width: 2560}, {Path: "/m/t5.gguf", Role: RoleT5, Width: 4096}, }} // Classified as an LLM: known at ANY width, which is what separates "the scan // rejected this pin" from "the scan never saw it". if !p.knowsLlm("/m/qwen3-4b.gguf") { t.Error("a scanned llm should be known") } if p.knowsLlm("/m/t5.gguf") { t.Error("a t5 is not an llm") } if p.knowsLlm("/m/never-scanned.gguf") || p.knowsLlm("") { t.Error("an unscanned or empty path is not known") } var nilPool *EncoderPool if nilPool.knowsLlm("/m/qwen3-4b.gguf") { t.Error("nil pool knows nothing") } } // writeGgufKVs lays down a gguf carrying only its header and the given KVs (no // tensors). String values are written as gguf strings, int64 as uint32, which is // all the pool's classification ladder reads. Keys are written in order, so // general.architecture must come first for the arch-prefixed keys to match. func writeGgufKVs(t *testing.T, path string, kvs [][2]any) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } f, err := os.Create(path) if err != nil { t.Fatal(err) } defer f.Close() le := binary.LittleEndian str := func(s string) { binary.Write(f, le, uint64(len(s))) f.WriteString(s) } f.WriteString("GGUF") binary.Write(f, le, uint32(3)) binary.Write(f, le, uint64(0)) binary.Write(f, le, uint64(len(kvs))) for _, kv := range kvs { str(kv[0].(string)) switch v := kv[1].(type) { case string: binary.Write(f, le, uint32(8)) str(v) case int: binary.Write(f, le, uint32(4)) binary.Write(f, le, uint32(v)) case bool: binary.Write(f, le, uint32(7)) var b uint8 if v { b = 1 } binary.Write(f, le, b) case []string: binary.Write(f, le, uint32(9)) binary.Write(f, le, uint32(8)) binary.Write(f, le, uint64(len(v))) for _, e := range v { str(e) } default: t.Fatalf("unsupported kv value %T", v) } } } // A recurrent LLM can never be a diffusion text encoder, so it must not enter // the pool even when it would win its width class. The hybrid paths sort first, // so with equal sizes and arch rank the old tiebreak handed Qwen-Image 2.1 the // hybrid (the MiMo-V2.6-Distill-Qwen-9B incident). func TestAutogen_EncoderPoolSkipsRecurrentLlm(t *testing.T) { root := t.TempDir() writeGgufKVs(t, filepath.Join(root, "a-hybrid", "MiMo-Distill-Qwen-9B-Q8_0.gguf"), [][2]any{ {"general.architecture", "qwen35"}, {"qwen35.embedding_length", 4096}, {"qwen35.full_attention_interval", 4}, }) writeGgufKVs(t, filepath.Join(root, "b-ssm", "Granite-Hybrid-Q8_0.gguf"), [][2]any{ {"general.architecture", "granitehybrid"}, {"granitehybrid.embedding_length", 4096}, {"granitehybrid.ssm.state_size", 128}, }) writeGgufKVs(t, filepath.Join(root, "c-vl", "Qwen3VL-8B-Instruct-Q8_0.gguf"), [][2]any{ {"general.architecture", "qwen3vl"}, {"qwen3vl.embedding_length", 4096}, }) p := ScanEncoderPool([]string{root}) for _, f := range p.Files { if f.Role == RoleLlm && !strings.Contains(f.Path, "Qwen3VL") { t.Errorf("recurrent llm in pool: %s", f.Path) } } if got, _ := p.Llm(4096, false, ""); !strings.Contains(got, "Qwen3VL") { t.Errorf("llm(4096) = %q, want the Qwen3-VL encoder", got) } }