{"record":{"id":"82b67448e37dbf80","repo":"SubtitleEdit/subtitleedit","slug":"no-vision-projector-found-next-to-fullpath-llam","errorCode":null,"errorMessage":"No vision projector found next to {fullPath}. llama.cpp OCR models need their mmproj sidecar; expected 'mmproj-{fileName}' or '{stem}-mmproj.gguf' in the same folder.","messagePattern":"No vision projector found next to (.+?)\\. llama\\.cpp OCR models need their mmproj sidecar; expected 'mmproj-(.+?)' or '(.+?)-mmproj\\.gguf' in the same folder\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/seconv/Core/LlamaCppOcrEngine.cs","lineNumber":156,"sourceCode":"        {\n            var name = requestedModel.Trim();\n\n            // Full path to a .gguf: use it directly, but require the mmproj sidecar - a vision\n            // model served without its projector can't see the image at all.\n            if (Path.IsPathRooted(name) || name.Contains(Path.DirectorySeparatorChar) || name.Contains(Path.AltDirectorySeparatorChar))\n            {\n                if (!File.Exists(name))\n                {\n                    throw new InvalidOperationException($\"OCR model file not found: {name}\");\n                }\n\n                var fullPath = Path.GetFullPath(name);\n                var mmproj = FindMmprojSidecar(fullPath);\n                if (mmproj == null)\n                {\n                    var fileName = Path.GetFileName(fullPath);\n                    var stem = Path.GetFileNameWithoutExtension(fullPath);\n                    throw new InvalidOperationException(\n                        $\"No vision projector found next to {fullPath}. llama.cpp OCR models need their mmproj sidecar; \" +\n                        $\"expected 'mmproj-{fileName}' or '{stem}-mmproj.gguf' in the same folder.\");\n                }\n\n                return new LlamaCppModel(Path.GetFileName(fullPath), fullPath, string.Empty, Url: string.Empty,\n                    MmprojFileName: mmproj);\n            }\n\n            // Name: match the curated OCR models (with or without .gguf).\n            var model = LlamaCppServerManager.OcrModels.FirstOrDefault(m => m.FileName.Equals(name, StringComparison.OrdinalIgnoreCase))\n                        ?? LlamaCppServerManager.OcrModels.FirstOrDefault(m => m.FileName.Equals(name + \".gguf\", StringComparison.OrdinalIgnoreCase))\n                        ?? LlamaCppServerManager.OcrModels.FirstOrDefault(m => m.DisplayName.Equals(name, StringComparison.OrdinalIgnoreCase));\n            if (model == null || !LlamaCppServerManager.IsModelInstalled(model))\n            {\n                throw new InvalidOperationException(\n                    $\"OCR model '{name}' not found in {LlamaCppServerManager.GetAndCreateModelsFolder()}. \" +\n                    \"Download one in Subtitle Edit (OCR window > llama.cpp) or pass a full path via --ocr-model. \" +\n                    $\"Curated models: {curatedNames}.\");","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/seconv/Core/LlamaCppOcrEngine.cs#L138-L174","documentation":"Thrown when a full .gguf model path exists (File.Exists passes) but no mmproj vision-projector sidecar file is found next to it. llama.cpp vision/OCR models need both the main model and a projector file; without the projector the model cannot process images at all. The engine searches for two naming conventions: 'mmproj-<filename>' (e.g. mmproj-model.gguf) and '<stem>-mmproj.gguf' (e.g. model-mmproj.gguf) in the same directory.","triggerScenarios":"Downloading only the main .gguf model without its mmproj sidecar; the sidecar has an unexpected name that matches neither convention; the sidecar is in a different directory than the model.","commonSituations":"Downloading a vision model from HuggingFace and missing the separate mmproj file download link; renaming the model file without renaming the sidecar; the model download was incomplete.","solutions":["Download the mmproj sidecar from the same model page and place it next to the main .gguf, named either 'mmproj-<filename>' or '<stem>-mmproj.gguf'.","If the sidecar has a different name, rename it to match one of the two expected conventions.","Verify both files exist in the same directory with a directory listing before running OCR."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"static string? FindMmproj(string modelPath)\n{\n    var dir = Path.GetDirectoryName(modelPath)!;\n    var fileName = Path.GetFileName(modelPath);\n    var stem = Path.GetFileNameWithoutExtension(modelPath);\n    return new[] { $\"mmproj-{fileName}\", $\"{stem}-mmproj.gguf\" }\n        .Select(n => Path.Combine(dir, n))\n        .FirstOrDefault(File.Exists);\n}\n// Before ResolveModel:\nif (FindMmproj(fullPath) == null)\n    Console.Error.WriteLine(\"Missing mmproj sidecar — download and place it next to the model.\");","typeGuard":"static bool HasMmprojSidecar(string modelPath) => FindMmproj(modelPath) != null;","tryCatchPattern":"try { var model = LlamaCppOcrEngine.ResolveModel(modelArg); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"No vision projector found\"))\n{ /* download mmproj sidecar and place next to model */ }","preventionTips":["Always download both the main model .gguf and its mmproj sidecar from the same model page.","Keep the mmproj file in the same directory as the model, following the expected naming convention.","After downloading, list the model directory to verify both files are present."],"tags":["llama-cpp","missing-file","ocr","model","vision-projector","dependencies"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}