---
title: evaluator
---

<div class="python-reference">
<a id="fastvideo.eval.evaluator-classes"></a>
<a id="fastvideo.eval.evaluator-functions"></a>
<a id="fastvideo.eval.evaluator.Evaluator-functions"></a>
<a id="fastvideo.eval.evaluator.Evaluator--parameters"></a>
<a id="fastvideo.eval.evaluator.Evaluator.evaluate--parameters"></a>
<a id="fastvideo.eval.evaluator.Evaluator.evaluate--returns"></a>
<a id="fastvideo.eval.evaluator"></a>
<p>User-facing scorer.</p>
<p>Layering (mirrors FastVideo's VideoGenerator → Worker pattern, but
in-process)::</p>
<pre><code>Evaluator               ← user-facing
  └── EvalWorker × N    ← single-GPU; owns metric replicas
  └── VideoPool         ← async path-→-tensor prefetch (per evaluate call)
</code></pre>
<p>The constructor builds one :class:<code>EvalWorker</code> per GPU and loads every
metric on every worker eagerly. :meth:<code>evaluate</code> is the single entry
point: pass kwargs for one sample, or pass a list of sample dicts to
fan-out across GPU replicas with pipelined decoding — same method,
return type follows the input shape.</p>
<h2 id="fastvideo.eval.evaluator.list_metrics"><code>fastvideo.eval.evaluator.list_metrics</code></h2>
<pre class="doc-signature"><code>list_metrics() -&gt; list&#91;str&#93;</code></pre>
<p>Return sorted list of all registered metric names.</p>
<h2 id="fastvideo.eval.evaluator.missing_dependencies"><code>fastvideo.eval.evaluator.missing_dependencies</code></h2>
<pre class="doc-signature"><code>missing_dependencies(metric_name: str) -&gt; list&#91;str&#93;</code></pre>
<p>Importable module names declared by <em>metric_name</em> that are not
actually importable in this environment. Returns <code>[]</code> if all deps
are satisfied or the metric is unknown.</p>
<p>Used by group-style resolution to decide which metrics to silently
skip (vs. naming a metric explicitly, where the missing dep should
surface as :class:<code>ImportError</code>).</p>
<h2 id="fastvideo.eval.evaluator.resolve_group"><code>fastvideo.eval.evaluator.resolve_group</code></h2>
<pre class="doc-signature"><code>resolve_group(name: str) -&gt; list&#91;str&#93; | None</code></pre>
<p>If <em>name</em> is a group prefix (e.g. <code>"vbench"</code>), return all matching
metric names.  Returns <code>None</code> if <em>name</em> is not a group.</p>
<h2 id="fastvideo.eval.evaluator.EvalResults"><code>fastvideo.eval.evaluator.EvalResults</code></h2>
<pre class="doc-signature"><code>EvalResults(samples: list&#91;dict&#91;str, MetricResult&#93;&#93; | None = None, corpus: dict&#91;str, MetricResult&#93; | None = None)</code></pre>
<p>Return type for :meth:<code>Evaluator.evaluate</code> with <code>samples=...</code>.</p>
<p>Behaves like a <code>list[dict[str, MetricResult]]</code> — one dict per
input sample, in input order — so existing iteration and indexing
keeps working. The <code>corpus</code> attribute carries set-metric results
(FAD, IS, …) that are properties of the whole input set, not of
any individual sample. Empty dict when no set metric ran.</p>

<h2 id="fastvideo.eval.evaluator.MetricResult"><code>fastvideo.eval.evaluator.MetricResult</code></h2>
<pre class="doc-signature"><code>MetricResult(name: str, score: float | None, details: dict&#91;str, Any&#93; = dict())</code></pre>
<p>Standard result container returned by all metrics.</p>
<p><code>score</code> is <code>None</code> when the metric was skipped (e.g. missing
required input).  Check <code>details["skipped"]</code> for the reason.</p>

<h2 id="fastvideo.eval.evaluator.EvalWorker"><code>fastvideo.eval.evaluator.EvalWorker</code></h2>
<pre class="doc-signature"><code>EvalWorker(metric_names: list&#91;str&#93;, device: str, *, compile: bool = False, pre_upload: bool = True, skip_missing_deps: bool = False)</code></pre>
<p>Owns metric replicas on one device. Single-GPU, single-sample.</p>
<p>Per-sample metrics (<code>is_set_metric=False</code>) return one
:class:<code>MetricResult</code> per <code>evaluate</code> call. Set metrics
(<code>is_set_metric=True</code>) accumulate state on the worker's own
instance and contribute nothing to the per-sample return — the
Evaluator finalizes them after the pool drains.</p>

<h3 id="fastvideo.eval.evaluator.EvalWorker.metric_names"><code>fastvideo.eval.evaluator.EvalWorker.metric_names</code></h3>
<pre class="doc-signature"><code>metric_names: list&#91;str&#93;</code></pre>
<p>Names of the metrics this worker owns, in load order.</p>
<h3 id="fastvideo.eval.evaluator.EvalWorker.evaluate"><code>fastvideo.eval.evaluator.EvalWorker.evaluate</code></h3>
<pre class="doc-signature"><code>evaluate(*, metrics: list&#91;str&#93; | None = None, **kwargs) -&gt; dict&#91;str, MetricResult&#93;</code></pre>
<p>Score one already-decoded sample.</p>
<p>Inputs (<code>video</code> / <code>reference</code> paths) are decoded and
normalized upstream by the :class:<code>VideoPool</code>. The worker
handles the optional pre-upload to its device and dispatches to
each metric's <code>compute</code> or <code>accumulate</code>.</p>
<p>Samples tagged <code>role="reference"</code> are corpus context for set
metrics only — per-sample metrics skip them so a mixed
Evaluator (e.g. FVD + LPIPS + vbench) doesn't produce spurious
per-sample scores on the reference half of the corpus.</p>
<p><code>metrics</code> (when not <code>None</code>) further restricts dispatch to
that subset of registered names — used by the per-call
<code>Evaluator.evaluate(metrics=...)</code> filter.</p>
<h3 id="fastvideo.eval.evaluator.EvalWorker.set_metrics"><code>fastvideo.eval.evaluator.EvalWorker.set_metrics</code></h3>
<pre class="doc-signature"><code>set_metrics() -&gt; dict&#91;str, BaseMetric&#93;</code></pre>
<p>Return <code>&#123;name: instance&#125;</code> for set metrics on this worker.</p>
<h3 id="fastvideo.eval.evaluator.EvalWorker.reset_set_metrics"><code>fastvideo.eval.evaluator.EvalWorker.reset_set_metrics</code></h3>
<pre class="doc-signature"><code>reset_set_metrics() -&gt; None</code></pre>
<p>Clear accumulator state on every set metric.</p>
<h3 id="fastvideo.eval.evaluator.EvalWorker.release_cuda_memory"><code>fastvideo.eval.evaluator.EvalWorker.release_cuda_memory</code></h3>
<pre class="doc-signature"><code>release_cuda_memory() -&gt; None</code></pre>
<p>Free CUDA caches without dropping models.</p>
<h3 id="fastvideo.eval.evaluator.EvalWorker.unload"><code>fastvideo.eval.evaluator.EvalWorker.unload</code></h3>
<pre class="doc-signature"><code>unload() -&gt; None</code></pre>
<p>Drop metric refs so models become GC-able. Reverse with reload().</p>
<h3 id="fastvideo.eval.evaluator.EvalWorker.reload"><code>fastvideo.eval.evaluator.EvalWorker.reload</code></h3>
<pre class="doc-signature"><code>reload() -&gt; None</code></pre>
<p>Rebuild metrics dropped by :meth:<code>unload</code>.</p>
<h2 id="fastvideo.eval.evaluator.init_logger"><code>fastvideo.eval.evaluator.init_logger</code></h2>
<pre class="doc-signature"><code>init_logger(name: str) -&gt; _FastvideoLogger</code></pre>
<p>The main purpose of this function is to ensure that loggers are
retrieved in such a way that we can be sure the root fastvideo logger has
already been configured.</p>
<h2 id="fastvideo.eval.evaluator.Evaluator"><code>fastvideo.eval.evaluator.Evaluator</code></h2>
<pre class="doc-signature"><code>Evaluator(metrics: list&#91;str&#93; | str = &#x27;all&#x27;, device: str = &#x27;cuda:0&#x27;, num_gpus: int = 1, compile: bool = False, *, loader_threads: int = 1, prefetch_factor: int = 2, pre_upload: bool = True, skip_missing_deps: bool = False)</code></pre>
<p>Pre-initialized scorer for repeated evaluation.</p>
<h2>Parameters</h2>
<p>metrics : list[str] | str
    Metric names, group prefixes (<code>"vbench"</code>), or <code>"all"</code>.
device : str
    Single-GPU device (e.g. <code>"cuda:0"</code>). Ignored when <em>num_gpus</em> &gt; 1.
num_gpus : int
    Number of GPU replicas. Each gets its own :class:<code>EvalWorker</code>.
compile : bool
    Apply :func:<code>torch.compile</code> to each metric's <code>_model</code>.
loader_threads : int
    Background decode threads in the :class:<code>VideoPool</code>. Default 1
    (hide decode behind compute). Bump for I/O-heavy benchmark sets
    where one loader can't keep up with the workers.
prefetch_factor : int
    <code>pool max_size = prefetch_factor * num_workers</code>. Default 2 —
    one sample being consumed, one prefetched per worker.
pre_upload : bool
    When <code>True</code> (default), the worker performs a single
    host→device upload of <code>video</code> / <code>reference</code> per sample
    before the metric loop, and every metric reads from that
    shared GPU-resident tensor. Without it, each metric pays its
    own <code>.to(self.device)</code> — N transfers of the same clip for N
    metrics, which dominates at high resolution. Set <code>False</code> for
    training-time eval, where keeping a clip resident on GPU
    across the metric loop would fight the training step for VRAM.
skip_missing_deps : bool
    When <code>True</code>, silently drop explicit metric names whose
    optional deps aren't importable (with a one-line warning per
    skipped metric). Default <code>False</code> — an explicit name with a
    missing dep raises :class:<code>ImportError</code> at construction time.
    Group selectors (<code>"vbench"</code>, <code>"all"</code>) always silent-skip
    regardless of this flag.</p>

<details><summary>Source</summary><pre><code>class Evaluator:
    &quot;&quot;&quot;Pre-initialized scorer for repeated evaluation.

    Parameters
    ----------
    metrics : list&#91;str&#93; | str
        Metric names, group prefixes (``&quot;vbench&quot;``), or ``&quot;all&quot;``.
    device : str
        Single-GPU device (e.g. ``&quot;cuda:0&quot;``). Ignored when *num_gpus* &gt; 1.
    num_gpus : int
        Number of GPU replicas. Each gets its own :class:`EvalWorker`.
    compile : bool
        Apply :func:`torch.compile` to each metric&#x27;s ``_model``.
    loader_threads : int
        Background decode threads in the :class:`VideoPool`. Default 1
        (hide decode behind compute). Bump for I/O-heavy benchmark sets
        where one loader can&#x27;t keep up with the workers.
    prefetch_factor : int
        ``pool max_size = prefetch_factor * num_workers``. Default 2 —
        one sample being consumed, one prefetched per worker.
    pre_upload : bool
        When ``True`` (default), the worker performs a single
        host→device upload of ``video`` / ``reference`` per sample
        before the metric loop, and every metric reads from that
        shared GPU-resident tensor. Without it, each metric pays its
        own ``.to(self.device)`` — N transfers of the same clip for N
        metrics, which dominates at high resolution. Set ``False`` for
        training-time eval, where keeping a clip resident on GPU
        across the metric loop would fight the training step for VRAM.
    skip_missing_deps : bool
        When ``True``, silently drop explicit metric names whose
        optional deps aren&#x27;t importable (with a one-line warning per
        skipped metric). Default ``False`` — an explicit name with a
        missing dep raises :class:`ImportError` at construction time.
        Group selectors (``&quot;vbench&quot;``, ``&quot;all&quot;``) always silent-skip
        regardless of this flag.
    &quot;&quot;&quot;

    def __init__(
        self,
        metrics: list&#91;str&#93; | str = &quot;all&quot;,
        device: str = &quot;cuda:0&quot;,
        num_gpus: int = 1,
        compile: bool = False,
        *,
        loader_threads: int = 1,
        prefetch_factor: int = 2,
        pre_upload: bool = True,
        skip_missing_deps: bool = False,
    ) -&gt; None:
        names = _resolve_metric_names(metrics, skip_missing_deps=skip_missing_deps)
        if num_gpus &gt; 1:
            self._workers = &#91;
                EvalWorker(names,
                           f&quot;cuda:&#123;i&#125;&quot;,
                           compile=compile,
                           pre_upload=pre_upload,
                           skip_missing_deps=skip_missing_deps) for i in range(num_gpus)
            &#93;
        else:
            self._workers = &#91;
                EvalWorker(names, device, compile=compile, pre_upload=pre_upload, skip_missing_deps=skip_missing_deps)
            &#93;
        self._loader_threads = max(1, loader_threads)
        self._prefetch_factor = max(1, prefetch_factor)

    @property
    def num_gpus(self) -&gt; int:
        return len(self._workers)

    @property
    def metric_names(self) -&gt; list&#91;str&#93;:
        return self._workers&#91;0&#93;.metric_names

    def evaluate(
        self,
        samples: Iterable&#91;dict&#93; | None = None,
        *,
        metrics: list&#91;str&#93; | None = None,
        **kwargs,
    ) -&gt; dict&#91;str, MetricResult&#93; | EvalResults:
        &quot;&quot;&quot;Score one sample (kwargs form) or many samples (list form).

        Both forms go through the same :class:`VideoPool` pipeline;
        ``video`` / ``reference`` paths are decoded asynchronously.

        Parameters
        ----------
        samples :
            Iterable of sample dicts.  Omit and pass kwargs for a
            single-sample call.
        metrics :
            Subset of this Evaluator&#x27;s registered metrics to actually
            run on this batch.  ``None`` (default) runs all registered.
            Lets a single long-lived Evaluator score different (gen,
            ref) corpora with different metric subsets across multiple
            ``evaluate()`` calls — e.g. LPIPS on a paired corpus, FVD
            on an unequal-cardinality corpus — without burning model
            loads.  Set-metric accumulators are reset only for the
            metrics included in *metrics*, so state for other set
            metrics is preserved across calls.

        Single sample::

            ev.evaluate(video=tensor, text_prompt=&quot;...&quot;, fps=24.0)

        Many samples::

            ev.evaluate(samples=&#91;&#123;&quot;video&quot;: ..., &quot;reference&quot;: ...&#125;, ...&#93;)

        Many samples with a metric filter::

            ev.evaluate(samples=lpips_samples, metrics=&#91;&quot;common.lpips&quot;&#93;)
            ev.evaluate(samples=fvd_samples,   metrics=&#91;&quot;common.fvd&quot;&#93;)

        Returns
        -------
        dict&#91;str, MetricResult&#93; for the single-sample form;
        :class:`EvalResults` (list-of-dict subclass with ``.corpus``) for
        the list form.
        &quot;&quot;&quot;
        if metrics is not None:
            unknown = &#91;m for m in metrics if m not in self.metric_names&#93;
            if unknown:
                raise ValueError(f&quot;metrics filter contains names not registered on this Evaluator: &quot;
                                 f&quot;&#123;unknown&#125;; registered: &#123;self.metric_names&#125;&quot;)

        single = samples is None
        sample_list: list&#91;dict&#93; = &#91;kwargs&#93; if samples is None else list(samples)
        if not sample_list:
            return EvalResults(samples=&#91;&#93;, corpus=&#123;&#125;)

        if single:
            set_names = self._workers&#91;0&#93;.set_metrics().keys()
            active_set = (set_names if metrics is None else (set_names &amp; set(metrics)))
            if active_set:
                # Set metrics need a population. A single sample can&#x27;t produce a
                # meaningful corpus result, and silently discarding it (return
                # ``per_sample&#91;0&#93;`` only) hides the no-op. Force the list form.
                raise ValueError(&quot;Set-vs-set metrics require samples=&#91;...&#93; with &gt;=2 entries; &quot;
                                 &quot;the kwargs form (single sample) cannot produce a corpus &quot;
                                 f&quot;result. Active set metrics: &#123;sorted(active_set)&#125;&quot;)

        per_sample, corpus = self._run(sample_list, metric_filter=metrics)

        if single:
            return per_sample&#91;0&#93;
        return EvalResults(samples=per_sample, corpus=corpus)

    def _run(
        self,
        samples: list&#91;dict&#93;,
        *,
        metric_filter: list&#91;str&#93; | None = None,
    ) -&gt; tuple&#91;list&#91;dict&#91;str, MetricResult&#93;&#93;, dict&#91;str, MetricResult&#93;&#93;:
        &quot;&quot;&quot;Pool-driven sample pipeline + set-metric finalize.

        ``metric_filter`` (when not ``None``) restricts both the worker
        dispatch and the set-metric finalize to that subset of names.
        Set metrics outside the filter keep their previous state.
        &quot;&quot;&quot;
        from fastvideo.eval.pool import VideoPool

        filter_set: set&#91;str&#93; | None = set(metric_filter) if metric_filter is not None else None

        # Reset only the set metrics that will actually run this batch —
        # other set metrics keep state across calls (e.g. FVD reference
        # features built by an earlier call still apply to this call).
        for w in self._workers:
            for name, m in w.set_metrics().items():
                if filter_set is None or name in filter_set:
                    m.reset()

        n_workers = len(self._workers)
        max_size = self._prefetch_factor * n_workers
        per_sample: list&#91;Any&#93; = &#91;None&#93; * len(samples)

        with VideoPool(samples, loader_threads=self._loader_threads, max_size=max_size) as pool:
            if n_workers == 1:
                while True:
                    item = pool.get()
                    if item is None:
                        break
                    idx, decoded = item
                    per_sample&#91;idx&#93; = self._workers&#91;0&#93;.evaluate(metrics=metric_filter, **decoded)
            else:
                # Multi-GPU: every worker drains the shared pool (work-stealing).
                errors: list&#91;BaseException&#93; = &#91;&#93;
                threads: list&#91;threading.Thread&#93; = &#91;&#93;
                for w in self._workers:
                    t = threading.Thread(target=self._consumer_loop,
                                         args=(w, pool, per_sample, errors, metric_filter),
                                         daemon=True)
                    t.start()
                    threads.append(t)
                for t in threads:
                    t.join()
                if errors:
                    raise errors&#91;0&#93;

        # Finalize only the set metrics in the filter.  Merge per-worker
        # state into worker 0, then finalize once.
        corpus: dict&#91;str, MetricResult&#93; = &#123;&#125;
        base_set = self._workers&#91;0&#93;.set_metrics()
        for name, m in base_set.items():
            if filter_set is not None and name not in filter_set:
                continue
            for w in self._workers&#91;1:&#93;:
                base_set&#91;name&#93;.merge_from(w.set_metrics()&#91;name&#93;)
            corpus&#91;name&#93; = m.finalize()

        return per_sample, corpus

    @staticmethod
    def _consumer_loop(
        worker: EvalWorker,
        pool: Any,
        results: list,
        errors: list,
        metric_filter: list&#91;str&#93; | None,
    ) -&gt; None:
        try:
            while True:
                item = pool.get()
                if item is None:
                    return
                idx, decoded = item
                results&#91;idx&#93; = worker.evaluate(metrics=metric_filter, **decoded)
        except BaseException as e:  # noqa: BLE001 — surface to parent thread via shared list
            errors.append(e)

    def release_cuda_memory(self) -&gt; None:
        &quot;&quot;&quot;Free CUDA caches on every replica without dropping models.&quot;&quot;&quot;
        for w in self._workers:
            w.release_cuda_memory()

    def unload(self) -&gt; None:
        &quot;&quot;&quot;Drop metric refs on every replica. Reverse with :meth:`reload`.&quot;&quot;&quot;
        for w in self._workers:
            w.unload()

    def reload(self) -&gt; None:
        &quot;&quot;&quot;Rebuild metrics dropped by :meth:`unload`.&quot;&quot;&quot;
        for w in self._workers:
            w.reload()

    def shutdown(self) -&gt; None:
        &quot;&quot;&quot;No-op; kept for API compatibility with older callers.&quot;&quot;&quot;</code></pre></details>
<h3 id="fastvideo.eval.evaluator.Evaluator.evaluate"><code>fastvideo.eval.evaluator.Evaluator.evaluate</code></h3>
<pre class="doc-signature"><code>evaluate(samples: Iterable&#91;dict&#93; | None = None, *, metrics: list&#91;str&#93; | None = None, **kwargs) -&gt; dict&#91;str, MetricResult&#93; | EvalResults</code></pre>
<p>Score one sample (kwargs form) or many samples (list form).</p>
<p>Both forms go through the same :class:<code>VideoPool</code> pipeline;
<code>video</code> / <code>reference</code> paths are decoded asynchronously.</p>
<h2>Parameters</h2>
<p>samples :
    Iterable of sample dicts.  Omit and pass kwargs for a
    single-sample call.
metrics :
    Subset of this Evaluator's registered metrics to actually
    run on this batch.  <code>None</code> (default) runs all registered.
    Lets a single long-lived Evaluator score different (gen,
    ref) corpora with different metric subsets across multiple
    <code>evaluate()</code> calls — e.g. LPIPS on a paired corpus, FVD
    on an unequal-cardinality corpus — without burning model
    loads.  Set-metric accumulators are reset only for the
    metrics included in <em>metrics</em>, so state for other set
    metrics is preserved across calls.</p>
<p>Single sample::</p>
<pre><code>ev.evaluate(video=tensor, text_prompt="...", fps=24.0)
</code></pre>
<p>Many samples::</p>
<pre><code>ev.evaluate(samples=[&#123;"video": ..., "reference": ...&#125;, ...])
</code></pre>
<p>Many samples with a metric filter::</p>
<pre><code>ev.evaluate(samples=lpips_samples, metrics=["common.lpips"])
ev.evaluate(samples=fvd_samples,   metrics=["common.fvd"])
</code></pre>
<h2>Returns</h2>
<p>dict[str, MetricResult] for the single-sample form;
:class:<code>EvalResults</code> (list-of-dict subclass with <code>.corpus</code>) for
the list form.</p>
<details><summary>Source</summary><pre><code>def evaluate(
    self,
    samples: Iterable&#91;dict&#93; | None = None,
    *,
    metrics: list&#91;str&#93; | None = None,
    **kwargs,
) -&gt; dict&#91;str, MetricResult&#93; | EvalResults:
    &quot;&quot;&quot;Score one sample (kwargs form) or many samples (list form).

    Both forms go through the same :class:`VideoPool` pipeline;
    ``video`` / ``reference`` paths are decoded asynchronously.

    Parameters
    ----------
    samples :
        Iterable of sample dicts.  Omit and pass kwargs for a
        single-sample call.
    metrics :
        Subset of this Evaluator&#x27;s registered metrics to actually
        run on this batch.  ``None`` (default) runs all registered.
        Lets a single long-lived Evaluator score different (gen,
        ref) corpora with different metric subsets across multiple
        ``evaluate()`` calls — e.g. LPIPS on a paired corpus, FVD
        on an unequal-cardinality corpus — without burning model
        loads.  Set-metric accumulators are reset only for the
        metrics included in *metrics*, so state for other set
        metrics is preserved across calls.

    Single sample::

        ev.evaluate(video=tensor, text_prompt=&quot;...&quot;, fps=24.0)

    Many samples::

        ev.evaluate(samples=&#91;&#123;&quot;video&quot;: ..., &quot;reference&quot;: ...&#125;, ...&#93;)

    Many samples with a metric filter::

        ev.evaluate(samples=lpips_samples, metrics=&#91;&quot;common.lpips&quot;&#93;)
        ev.evaluate(samples=fvd_samples,   metrics=&#91;&quot;common.fvd&quot;&#93;)

    Returns
    -------
    dict&#91;str, MetricResult&#93; for the single-sample form;
    :class:`EvalResults` (list-of-dict subclass with ``.corpus``) for
    the list form.
    &quot;&quot;&quot;
    if metrics is not None:
        unknown = &#91;m for m in metrics if m not in self.metric_names&#93;
        if unknown:
            raise ValueError(f&quot;metrics filter contains names not registered on this Evaluator: &quot;
                             f&quot;&#123;unknown&#125;; registered: &#123;self.metric_names&#125;&quot;)

    single = samples is None
    sample_list: list&#91;dict&#93; = &#91;kwargs&#93; if samples is None else list(samples)
    if not sample_list:
        return EvalResults(samples=&#91;&#93;, corpus=&#123;&#125;)

    if single:
        set_names = self._workers&#91;0&#93;.set_metrics().keys()
        active_set = (set_names if metrics is None else (set_names &amp; set(metrics)))
        if active_set:
            # Set metrics need a population. A single sample can&#x27;t produce a
            # meaningful corpus result, and silently discarding it (return
            # ``per_sample&#91;0&#93;`` only) hides the no-op. Force the list form.
            raise ValueError(&quot;Set-vs-set metrics require samples=&#91;...&#93; with &gt;=2 entries; &quot;
                             &quot;the kwargs form (single sample) cannot produce a corpus &quot;
                             f&quot;result. Active set metrics: &#123;sorted(active_set)&#125;&quot;)

    per_sample, corpus = self._run(sample_list, metric_filter=metrics)

    if single:
        return per_sample&#91;0&#93;
    return EvalResults(samples=per_sample, corpus=corpus)</code></pre></details>
<h3 id="fastvideo.eval.evaluator.Evaluator.release_cuda_memory"><code>fastvideo.eval.evaluator.Evaluator.release_cuda_memory</code></h3>
<pre class="doc-signature"><code>release_cuda_memory() -&gt; None</code></pre>
<p>Free CUDA caches on every replica without dropping models.</p>
<details><summary>Source</summary><pre><code>def release_cuda_memory(self) -&gt; None:
    &quot;&quot;&quot;Free CUDA caches on every replica without dropping models.&quot;&quot;&quot;
    for w in self._workers:
        w.release_cuda_memory()</code></pre></details>
<h3 id="fastvideo.eval.evaluator.Evaluator.unload"><code>fastvideo.eval.evaluator.Evaluator.unload</code></h3>
<pre class="doc-signature"><code>unload() -&gt; None</code></pre>
<p>Drop metric refs on every replica. Reverse with :meth:<code>reload</code>.</p>
<details><summary>Source</summary><pre><code>def unload(self) -&gt; None:
    &quot;&quot;&quot;Drop metric refs on every replica. Reverse with :meth:`reload`.&quot;&quot;&quot;
    for w in self._workers:
        w.unload()</code></pre></details>
<h3 id="fastvideo.eval.evaluator.Evaluator.reload"><code>fastvideo.eval.evaluator.Evaluator.reload</code></h3>
<pre class="doc-signature"><code>reload() -&gt; None</code></pre>
<p>Rebuild metrics dropped by :meth:<code>unload</code>.</p>
<details><summary>Source</summary><pre><code>def reload(self) -&gt; None:
    &quot;&quot;&quot;Rebuild metrics dropped by :meth:`unload`.&quot;&quot;&quot;
    for w in self._workers:
        w.reload()</code></pre></details>
<h3 id="fastvideo.eval.evaluator.Evaluator.shutdown"><code>fastvideo.eval.evaluator.Evaluator.shutdown</code></h3>
<pre class="doc-signature"><code>shutdown() -&gt; None</code></pre>
<p>No-op; kept for API compatibility with older callers.</p>
<details><summary>Source</summary><pre><code>def shutdown(self) -&gt; None:
    &quot;&quot;&quot;No-op; kept for API compatibility with older callers.&quot;&quot;&quot;</code></pre></details>
<h2 id="fastvideo.eval.evaluator.create_evaluator"><code>fastvideo.eval.evaluator.create_evaluator</code></h2>
<pre class="doc-signature"><code>create_evaluator(metrics: list&#91;str&#93; | str = &#x27;all&#x27;, device: str = &#x27;cuda:0&#x27;, num_gpus: int = 1, compile: bool = False, *, skip_missing_deps: bool = False) -&gt; Evaluator</code></pre>

<details><summary>Source</summary><pre><code>def create_evaluator(
    metrics: list&#91;str&#93; | str = &quot;all&quot;,
    device: str = &quot;cuda:0&quot;,
    num_gpus: int = 1,
    compile: bool = False,
    *,
    skip_missing_deps: bool = False,
) -&gt; Evaluator:
    return Evaluator(metrics=metrics,
                     device=device,
                     num_gpus=num_gpus,
                     compile=compile,
                     skip_missing_deps=skip_missing_deps)</code></pre></details>
</div>
