Repository: SuperDuperDB/superduperdb
File: superduperdb/components/model.py (Python)

Code before:
                    self.device = device
                    return
                except Exception:
                    if i == len(self.preferred_devices) - 1:
                        raise

    @property
    def training_keys(self) -> t.List:
        if isinstance(self.train_X, list):
            out = list(self.train_X)
        elif self.train_X is not None:
            out = [self.train_X]
        if self.train_y is not None:
            if isinstance(self.train_y, list):
                out.extend(self.train_y)
            else:
                out.append(self.train_y)
        return out

    def append_metrics(self, d: t.Dict[str, float]) -> None:
        if self.metric_values is not None:
            for k, v in d.items():
                self.metric_values.setdefault(k, []).append(v)

    def validate(
        self, db, validation_set: t.Union[Dataset, str], metrics: t.Sequence[Metric]
    ):
        db.add(self)
        out = self._validate(db, validation_set, metrics)
        if self.metric_values is None:
            raise ValueError('self.metric_values cannot be None')
        self.metric_values.update(out)
        db.metadata.update_object(
            type_id='model',
            identifier=self.identifier,
            version=self.version,
            key='dict.metric_values',
            value=self.metric_values,
        )

    def pre_create(self, db: Datalayer):
        # TODO this kind of thing should come from an enum component_types.datatype
        # that will make refactors etc. easier
        if isinstance(self.datatype, str):
            # ruff: noqa: E501
            self.datatype = db.load('datatype', self.datatype)  # type: ignore[assignment]

    def _validate(
        self,
        db: Datalayer,
        validation_set: t.Union[Dataset, str],

Review comment (performance):
How about X is exactly a list as one parameter?
For example: 
1. Batch inference `model.predict(["1", "2"])`
2. Text similarity `model.predict(("This is a apple", "There are two apple"))`


Code after:
    :param device: The device to use
    :param preferred_devices: The preferred devices to use
    :param training_configuration: The training configuration
    :param train_X: The key of the input data to use for training
    :param train_y: The key of the target data to use for training
    :param train_select: The select to use for training
    """

    __doc__ = __doc__.format(
        component_params=Component.__doc__,
        _predictor_params=_Predictor.__doc__,
    )

    _artifacts: t.ClassVar[t.Sequence[t.Tuple[str, 'DataType']]] = (
        ('object', dill_serializer),
    )

    object: t.Any
    model_to_device_method: t.Optional[str] = None
    metric_values: t.Optional[t.Dict] = dc.field(default_factory=dict)
    predict_method: t.Optional[str] = None
    model_update_kwargs: dict = dc.field(default_factory=dict)
    device: str = "cpu"
    preferred_devices: t.Union[None, t.Sequence[str]] = ("cuda", "mps", "cpu")

    training_configuration: t.Union[str, _TrainingConfiguration, None] = None
    train_X: t.Optional[str] = None
    train_y: t.Optional[str] = None
    train_select: t.Optional[CompoundSelect] = None

    type_id: t.ClassVar[str] = 'model'

    def __post_init__(self, artifacts):
        super().__post_init__(artifacts)
        self._artifact_method = None
        if self.model_to_device_method is not None:
            self._artifact_method = getattr(self, self.model_to_device_method)

    def __repr__(self):
        s = f'Identifier: {self.identifier}\nParams: {self.inputs.params}\nPreferred Devices: {self.preferred_devices}'
        return border_msg(s, title='SuperDuper Model')

    def to_call(self, X, *args, **kwargs):
        X = self.setup_required_inputs(X)
        if self.predict_method is None:
            return self.object(*args, **X, **kwargs)
        out = getattr(self.object, self.predict_method)(*args, **X, **kwargs)
        return out

    def post_create(self, db: Datalayer) -> None:
        if isinstance(self.training_configuration, str):