{"record":{"id":"8e0448823eee8314","repo":"vllm-project/vllm","slug":"method-custom-class-requires-model-to-contain","errorCode":null,"errorMessage":"method='custom_class' requires 'model' to contain the custom proposer module path (e.g., 'my_module.MyProposer').","messagePattern":"method='custom_class' requires 'model' to contain the custom proposer module path \\(e\\.g\\., 'my_module\\.MyProposer'\\)\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"vllm/config/speculative.py","lineNumber":791,"sourceCode":"                # DeepSeek DSpark can ship the weights inside the target checkpoint\n                if self.target_model_config is None:\n                    raise ValueError(\"target_model_config must be present for dspark\")\n                self.model = self.target_model_config.model\n                if not self.quantization:\n                    self.quantization = self.target_model_config.quantization\n            elif self.method in (\"ngram\", \"[ngram]\"):\n                self.model = \"ngram\"\n            elif self.method == \"ngram_gpu\":\n                self.model = \"ngram_gpu\"\n            elif self.method == \"suffix\":\n                self.model = \"suffix\"\n            elif self.method == \"extract_hidden_states\":\n                self.model = \"extract_hidden_states\"\n            elif self.method == \"custom_class\":\n                # method was set explicitly, but model should already contain the\n                # custom module path. If not, this is a configuration error.\n                if self.model is None:\n                    raise ValueError(\n                        \"method='custom_class' requires 'model' to contain the \"\n                        \"custom proposer module path (e.g., 'my_module.MyProposer').\"\n                    )\n            else:\n                raise ValueError(\n                    \"num_speculative_tokens was provided but without speculative model.\"\n                )\n\n        if self.method in (\"ngram\", \"[ngram]\"):\n            self.method = \"ngram\"\n\n        if self.method in (\"ngram\", \"ngram_gpu\"):\n            # Set default values if not provided\n            if self.prompt_lookup_min is None and self.prompt_lookup_max is None:\n                # TODO(woosuk): Tune these values. They are arbitrarily chosen.\n                self.prompt_lookup_min = 5\n                self.prompt_lookup_max = 5\n            elif self.prompt_lookup_min is None:","sourceCodeStart":773,"sourceCodeEnd":809,"githubUrl":"https://github.com/vllm-project/vllm/blob/c794754062d49a8fdb63ab3c5215b488b865030c/vllm/config/speculative.py#L773-L809","documentation":"For method='custom_class', the draft proposer is user-supplied Python: the 'model' field must carry an importable 'module.ClassName' path. Unlike other methods there is nothing to auto-fill, so if model is None the config is rejected rather than silently loading nothing.","triggerScenarios":"SpeculativeConfig(method='custom_class', num_speculative_tokens=3) with no model; passing the class object in another field instead of the dotted path string; typo in the method name leaving model unset.","commonSituations":"Migrating from ngram/MTP configs and forgetting that custom_class has no default proposer; assuming model only names HF checkpoints.","solutions":["Set model to the dotted path of your proposer class, e.g. model='my_plugin.proposers.MyProposer'","Ensure the module is importable in the vLLM process (installed or on PYTHONPATH)","Verify the class is a registered/compatible speculative proposer before launch"],"exampleFix":"# before\nSpeculativeConfig(method='custom_class', num_speculative_tokens=3)\n\n# after\nSpeculativeConfig(method='custom_class', num_speculative_tokens=3, model='my_plugin.proposers.MyProposer')","handlingStrategy":"validation","validationCode":"import importlib\n\ndef proposer_path_ok(path: str | None) -> bool:\n    if not isinstance(path, str) or '.' not in path:\n        return False\n    mod, cls = path.rsplit('.', 1)\n    try:\n        return hasattr(importlib.import_module(mod), cls)\n    except ImportError:\n        return False","typeGuard":"def is_custom_class_path(v: object) -> bool:\n    return isinstance(v, str) and '.' in v and v.rsplit('.', 1)[1].isidentifier()","tryCatchPattern":"null","preventionTips":["Always pair method='custom_class' with a module.Class model path","Verify importability of the proposer module in the serving environment"],"tags":["speculative-decoding","custom-class","config","api-misuse"],"backgroundTag":null,"analyzedSha":"c794754062d49a8fdb63ab3c5215b488b865030c","analyzedAt":"2026-08-14T21:17:39.825Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}