huggingface/transformers · critical · ValueError

No real solution (discriminant = {})

Error message

No real solution (discriminant = {})

What it means

Raised by PagedAttentionCache._solve_quadratic when the discriminant of the memory-polynomial quadratic is negative, i.e. the equation a·x²+b·x+c=0 has no real root. In practice this means the memory constraint curve never crosses zero, so no batch size can satisfy it — the budget is mathematically unreachable for the given coefficients (e.g. constant memory cost alone exceeds available memory while the quadratic terms grow).

Source

Thrown at src/transformers/generation/continuous_batching/cache.py:747

    def _check_footprint(self, max_batch_tokens: int, num_blocks: int) -> tuple[int, int]:
        """Checks if the footprint of the cache is within the available memory."""
        memory_footprint = self.compute_memory_footprint(max_batch_tokens, num_blocks)
        if memory_footprint > self.available_memory:
            raise MemoryError(
                f"Memory footprint {memory_footprint} is more than available memory {self.available_memory}"
            )
        if max_batch_tokens <= 0 or num_blocks <= 0:
            raise ValueError(f"Invalid values: max_batch_tokens = {max_batch_tokens}, num_blocks = {num_blocks}")
        return max_batch_tokens, num_blocks

    def _solve_quadratic(self, a: float, b: float, c: float) -> int:
        """Largest positive root of a·x² + b·x + c = 0. Falls back to linear when a == 0. Rounded down."""
        if a == 0:
            return int(-c / b)
        discriminant = b**2 - 4 * a * c
        if discriminant < 0:
            raise ValueError(f"No real solution (discriminant = {discriminant})")
        root = (-b + sqrt(discriminant)) / (2 * a)
        if root < 0:
            raise ValueError(f"No positive solution (root = {root})")
        return int(floor(root))

    # Formatting is disabled because of comment indentation, which improves readability.
    # fmt: off
    def _equation_coefficients(self, peak_deltas: tuple[int, ...]) -> tuple[int, ...]:
        """Given some deltas corresponding to an activation peak, returns the coefficients for the memory polynomial of
        that peak. The memory polynomial is described in that class docstring."""
        delta_m, delta_n, delta_mm, delta_mn = peak_deltas

        i = torch.int32.itemsize             # size of int32 in bytes, used for index, input_ids, ...
        a = self.activation_dtype.itemsize             # for now, the cache and the activation have the same dtype
        c = self.cache_dtype.itemsize
        k = self.io_multiplier               # 1 sync, 2 async (IO tensors only)

        # -- N terms: cost per cache page --------------------------------------------------

View on GitHub (pinned to a597f97485)

Solutions

  1. Reduce num_blocks (if you set it explicitly) so the fixed cache cost fits available memory
  2. Free memory / use a smaller or quantized model to raise available_memory
  3. Use a smaller cache dtype to shrink the N-dependent coefficient
  4. Let the cache auto-size by passing None for both values with a sane cache_fill_per_batch instead of forcing one variable

Example fix

# before
cfg = ContinuousBatchingConfig(num_blocks=4096)  # fixed KV cost alone > available_memory

# after
cfg = ContinuousBatchingConfig(num_blocks=512)
Defensive patterns

Strategy: validation

Validate before calling

import torch
free_b, _ = torch.cuda.mem_get_info()
# rough KV bytes per block: 2 (k,v) * layers * block_size * kv_heads * head_dim * dtype_size
kv_per_block = 2 * num_layers * block_size * num_kv_heads * head_dim * 2
if cfg.num_blocks is not None and cfg.num_blocks * kv_per_block > free_b:
    cfg.num_blocks = int(free_b * 0.8 // kv_per_block)

Try / catch

try:
    manager = model.continuous_batching(config=cfg)
except ValueError as e:
    if 'No real solution' in str(e) and cfg.num_blocks:
        cfg.num_blocks //= 2
        manager = model.continuous_batching(config=cfg)
    else:
        raise

Prevention

When it happens

Trigger: Solving for max_batch_tokens given num_blocks (N given branch) or auto-sizing both via cache_fill_per_batch, when available_memory is smaller than the constant term of the polynomial (cn·N - avail makes c so negative/positive that no real root exists).

Common situations: Very small GPU or near-zero available_memory; num_blocks set so large that even zero max_batch_tokens overshoots memory; incorrect activation_peak coefficients for an exotic architecture.

Related errors


AI-assisted analysis of huggingface/transformers@a597f97485 (2026-08-14). Data as JSON: /api/errors/1390bded0bc93c7a. Report an issue: GitHub.