microsoft/qlib · error · NotImplementedError

optimizer {} is not supported!

Error message

optimizer {} is not supported!

What it means

GATsTSModel.fit() builds its training optimizer from the optimizer hyperparameter with exactly two branches: 'adam' -> torch.optim.Adam and 'gd' -> torch.optim.SGD (both case-insensitive). Any other string raises NotImplementedError before training starts. The time-series GATs variant shares the same constrained optimizer switch as the non-ts version.

Source

Thrown at qlib/contrib/model/pytorch_gats_ts.py:155

            np.random.seed(self.seed)
            torch.manual_seed(self.seed)

        self.GAT_model = GATModel(
            d_feat=self.d_feat,
            hidden_size=self.hidden_size,
            num_layers=self.num_layers,
            dropout=self.dropout,
            base_model=self.base_model,
        )
        self.logger.info("model:\n{:}".format(self.GAT_model))
        self.logger.info("model size: {:.4f} MB".format(count_parameters(self.GAT_model)))

        if optimizer.lower() == "adam":
            self.train_optimizer = optim.Adam(self.GAT_model.parameters(), lr=self.lr)
        elif optimizer.lower() == "gd":
            self.train_optimizer = optim.SGD(self.GAT_model.parameters(), lr=self.lr)
        else:
            raise NotImplementedError("optimizer {} is not supported!".format(optimizer))

        self.fitted = False
        self.GAT_model.to(self.device)

    @property
    def use_gpu(self):
        return self.device != torch.device("cpu")

    def mse(self, pred, label):
        loss = (pred - label) ** 2
        return torch.mean(loss)

    def loss_fn(self, pred, label):
        mask = ~torch.isnan(label)

        if self.loss == "mse":
            return self.mse(pred[mask], label[mask])

View on GitHub (pinned to 79633dd950)

Solutions

  1. Use 'adam' or 'gd' as the optimizer value.
  2. Replace 'sgd' with 'gd'.
  3. Subclass GATsTSModel and add a branch for the torch.optim optimizer you need.

Example fix

# before
model = GATsTSModel(optimizer='adamw')

# after
model = GATsTSModel(optimizer='adam')
Defensive patterns

Strategy: validation

Validate before calling

assert optimizer.lower() in ('adam', 'gd'), f"optimizer must be 'adam' or 'gd', got {optimizer!r}"

Type guard

def is_supported_optimizer(name: str) -> bool:
    return name.lower() in ('adam', 'gd')

Try / catch

try:
    model.fit(dataset)
except NotImplementedError as e:
    if 'optimizer' in str(e):
        model = GATsTSModel(optimizer='adam')
        model.fit(dataset)
    else:
        raise

Prevention

When it happens

Trigger: GATsTSModel(...).fit(dataset) with optimizer='sgd', 'adamw', 'rmsprop', or any string besides 'adam'/'gd'.

Common situations: Writing 'sgd' instead of the expected 'gd'; migrating configs from models with wider optimizer support; attempting to use decoupled weight-decay optimizers like AdamW which are not wired in.

Related errors


AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15). Data as JSON: /api/errors/6b14a8092530925e. Report an issue: GitHub.