keras-team/keras · error · ValueError

Argument `metric_variables` must be a list of tensors corres

Error message

Argument `metric_variables` must be a list of tensors corresponding 1:1 to {self.__class__.__name__}().variables. Received list with length {len(metric_variables)}, but expected {len(self.variables)} variables.

What it means

Raised by Metric.stateless_update_state when the metric_variables list length differs from the metric's own variables count. Keras 3's stateless metric API requires the caller to pass exactly the tensors previously captured from metric.variables on the same metric object; it is used internally by train_step/test_step.

Source

Thrown at keras/src/metrics/metric.py:123

            }
        )

    def reset_state(self):
        """Reset all of the metric state variables.

        This function is called between epochs/steps,
        when a metric is evaluated during training.
        """
        for v in self.variables:
            v.assign(ops.zeros(v.shape, dtype=v.dtype))

    def update_state(self, *args, **kwargs):
        """Accumulate statistics for the metric."""
        raise NotImplementedError

    def stateless_update_state(self, metric_variables, *args, **kwargs):
        if len(metric_variables) != len(self.variables):
            raise ValueError(
                "Argument `metric_variables` must be a list of tensors "
                f"corresponding 1:1 to {self.__class__.__name__}().variables. "
                f"Received list with length {len(metric_variables)}, but "
                f"expected {len(self.variables)} variables."
            )
        # Gather variable mapping
        mapping = list(zip(self.variables, metric_variables))

        # Call in stateless scope
        with backend.StatelessScope(state_mapping=mapping) as scope:
            self.update_state(*args, **kwargs)

        # Gather updated variables
        metric_variables = []
        for v in self.variables:
            new_v = scope.get_current_value(v)
            if new_v is not None:
                metric_variables.append(new_v)

View on GitHub (pinned to 7a34a03db6)

Solutions

  1. Capture and pass the full list from the same metric: vars = list(m.variables).
  2. Never mix variable lists between metric instances.
  3. Build the metric (one update_state or explicit build) before snapshotting variables.

Example fix

# before
m = keras.metrics.BinaryAccuracy()
snap = m.variables                  # possibly empty/stale
m.stateless_update_state([snap[0]], y_true, y_pred)

# after
m = keras.metrics.BinaryAccuracy()
m.update_state(y_true, y_pred)      # build variables first
snap = list(m.variables)             # full 1:1 list
m.stateless_update_state(snap, y_true, y_pred)
Defensive patterns

Strategy: validation

Validate before calling

vars_ = list(m.variables)
assert len(vars_) == len(metric_variables), 'variable list out of sync'
m.stateless_update_state(vars_, *args)

Try / catch

try:
    m.stateless_update_state(vars_, y_true, y_pred)
except ValueError:
    vars_ = list(m.variables)  # re-capture after build
    m.stateless_update_state(vars_, y_true, y_pred)

Prevention

When it happens

Trigger: Calling m.stateless_update_state(vars_from_other_metric, y_true, y_pred); passing a subset like [v[0]] for a metric with 2+ variables; snapshotting m.variables before the metric built its state (empty vs populated).

Common situations: Custom functional/JAX training loops threading metric state manually; averaging metrics across replicas; variable lists captured at the wrong lifecycle point.

Related errors


AI-assisted analysis of keras-team/keras@7a34a03db6 (2026-08-25). Data as JSON: /api/errors/1c79be9c96eb9113. Report an issue: GitHub.