{"record":{"id":"cc40b8883e2c0b0f","repo":"microsoft/qlib","slug":"unknown-loss-s-cc40b8","errorCode":null,"errorMessage":"unknown loss `%s`","messagePattern":"unknown loss `(.+?)`","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"qlib/contrib/model/pytorch_sfm.py","lineNumber":426,"sourceCode":"                    break\n\n        self.logger.info(\"best score: %.6lf @ %d\" % (best_score, best_epoch))\n        self.sfm_model.load_state_dict(best_param)\n        torch.save(best_param, save_path)\n        if self.device != \"cpu\":\n            torch.cuda.empty_cache()\n\n    def mse(self, pred, label):\n        loss = (pred - label) ** 2\n        return torch.mean(loss)\n\n    def loss_fn(self, pred, label):\n        mask = ~torch.isnan(label)\n\n        if self.loss == \"mse\":\n            return self.mse(pred[mask], label[mask])\n\n        raise ValueError(\"unknown loss `%s`\" % self.loss)\n\n    def metric_fn(self, pred, label):\n        mask = torch.isfinite(label)\n\n        if self.metric in (\"\", \"loss\"):\n            return -self.loss_fn(pred[mask], label[mask])\n\n        raise ValueError(\"unknown metric `%s`\" % self.metric)\n\n    def predict(self, dataset: DatasetH, segment: Union[Text, slice] = \"test\"):\n        if not self.fitted:\n            raise ValueError(\"model is not fitted yet!\")\n\n        x_test = dataset.prepare(segment, col_set=\"feature\", data_key=DataHandlerLP.DK_I)\n        index = x_test.index\n        self.sfm_model.eval()\n        x_values = x_test.values\n        sample_num = x_values.shape[0]","sourceCodeStart":408,"sourceCodeEnd":444,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/contrib/model/pytorch_sfm.py#L408-L444","documentation":"Raised by SFM model loss_fn in qlib/contrib/model/pytorch_sfm.py:426 when self.loss is not 'mse'. The SFM model supports only masked MSE (NaN labels are masked out); any other loss string is a ValueError thrown on the first loss evaluation in fit(). The metric_fn ('', 'loss') also delegates to loss_fn, so a bad loss breaks both.","triggerScenarios":"Setting loss to anything except 'mse' in SFM model kwargs and calling fit(); the first train_epoch/test_epoch evaluation triggers the raise.","commonSituations":"Copying loss names from other frameworks or other qlib models; hand-editing a workflow YAML and introducing a typo like 'msae' or 'MSE'.","solutions":["Set loss: 'mse' (only supported value).","Subclass the SFM model and override loss_fn(), preserving the NaN mask, for custom losses."],"exampleFix":"# before\nkwargs:\n  loss: huber\n\n# after\nkwargs:\n  loss: mse","handlingStrategy":"validation","validationCode":"assert config.get(\"loss\", \"mse\") == \"mse\", \"SFM model only supports loss='mse'\"","typeGuard":"def is_supported_sfm_loss(loss: str) -> bool:\n    return loss == \"mse\"","tryCatchPattern":"try:\n    model.fit(dataset)\nexcept ValueError as e:\n    if \"unknown loss\" in str(e):\n        raise ValueError(\"SFM supports only loss='mse'\") from e\n    raise","preventionTips":["Per-model config validation: the accepted loss set differs between qlib models.","Default loss to 'mse' and omit from configs unless intentionally changed."],"tags":["qlib","pytorch","loss-function","config","sfm"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}