vllm-project/vllm · error · ValueError

Cannot merge nested option {'.'.join(path)!r}: {part!r} is a

Error message

Cannot merge nested option {'.'.join(path)!r}: {part!r} is already a scalar

What it means

merge_value() builds a nested dict from dotted CLI options (e.g. --quantization.pratio=0.5). While walking each path segment it requires that intermediate segments be dicts; if an earlier option already stored a scalar at that segment (or YAML alias collision), merging a nested key under it raises this ValueError.

Source

Thrown at tools/recipes/recipe_json_to_vllm_config.py:425


def is_option_token(token: str) -> bool:
    if token.startswith("--"):
        return True
    if token in SHORT_ALIASES or token == "-O":
        return True
    return token.startswith("-O") and len(token) > 2


def merge_value(dst: dict[str, Any], path: list[str], value: Any) -> None:
    """Merge dotted CLI args into nested YAML dictionaries."""
    cur = dst
    for part in path[:-1]:
        existing = cur.get(part)
        if existing is None:
            cur[part] = {}
        elif not isinstance(existing, dict):
            raise ValueError(
                f"Cannot merge nested option {'.'.join(path)!r}: "
                f"{part!r} is already a scalar"
            )
        cur = cur[part]

    leaf = path[-1]
    if leaf not in cur:
        cur[leaf] = value
        return

    # Repeated CLI option. Preserve all values.
    old = cur[leaf]
    if not isinstance(old, list):
        old = [old]
    if isinstance(value, list):
        old.extend(value)
    else:
        old.append(value)

View on GitHub (pinned to c794754062)

Solutions

  1. Inspect the recipe's argv (jq '.argv' recipe.json) and find the two options sharing the dotted prefix
  2. Fix the source recipe: use the nested form consistently (--rope-scaling.rope-type plus --rope-scaling.factor) and drop the conflicting scalar form
  3. If the collision comes from normalization, rename one option in the recipe JSON or patch SHORT_ALIASES/normalize_key handling locally
  4. Report the offending argv to the Recipes maintainers if the recipe is server-rendered

Example fix

# before (conflicting scalar + nested forms in argv)
["vllm","serve","m","--rope-scaling","1.0","--rope-scaling.rope-type","linear"]
# after (nested form only)
["vllm","serve","m","--rope-scaling.rope-type","linear","--rope-scaling.factor","8.0"]
Defensive patterns

Strategy: validation

Validate before calling

def check_no_prefix_collisions(argv: list[str]) -> None:
    keys = set()
    for t in argv:
        if t.startswith("--") and "=" in t:
            keys.add(t[2:].split("=", 1)[0])
    for k in keys:
        for other in keys:
            if other != k and other.startswith(k + "."):
                raise SystemExit(f"option prefix collision: {k} vs {other}")

Type guard

def option_is_mergeable(existing: object) -> bool:
    return existing is None or isinstance(existing, dict)

Prevention

When it happens

Trigger: A recipe argv containing both a scalar option and a nested option sharing a prefix, e.g. `--rope-scaling 1.0` followed by `--rope-scaling.rope-type linear`, or two Recipes-rendered options whose normalized keys collide after underscore->dash normalization.

Common situations: Recipes API starts emitting compound/dotted options that overlap with existing flat flags; hand-edited recipe JSON with conflicting options; normalization (underscores to dashes in the top-level key) accidentally mapping two distinct keys onto the same path.

Related errors


AI-assisted analysis of vllm-project/vllm@c794754062 (2026-08-14). Data as JSON: /api/errors/934fa29749fb2aff. Report an issue: GitHub.