vllm-project/vllm · error · ValueError

Malformed zmq_address {zmq_address!r}: expected 'host:IP,han

Error message

Malformed zmq_address {zmq_address!r}: expected 'host:IP,handshake:PORT,notify:PORT' format

What it means

Thrown by parse_moriio_zmq_address in the MoRI-IO KV-transfer connector when the zmq_address string cannot be parsed. The connector requires the exact comma-separated form 'host:IP,handshake:PORT,notify:PORT'; a missing host/handshake/notify key (KeyError) or a non-numeric port (ValueError) is re-raised as this ValueError.

Source

Thrown at vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_common.py:411

    Parses ``"host:IP,handshake:PORT,notify:PORT"`` into
        (host, handshake_port, notify_port).

    Each key-value pair is split on the *first* colon so that IPv6 addresses
    (e.g. ``host:::1``) are handled correctly.  Raises ``ValueError`` if any
    of ``host``, ``handshake``, or ``notify`` keys are absent or if the port
    values are non-numeric.
    """
    parts: dict[str, str] = {}
    for segment in zmq_address.split(","):
        key, _, val = segment.partition(":")
        parts[key.strip()] = val.strip()
    try:
        host = parts["host"]
        handshake_port = int(parts["handshake"])
        notify_port = int(parts["notify"])
    except (KeyError, ValueError) as e:
        raise ValueError(
            f"Malformed zmq_address {zmq_address!r}: expected "
            f"'host:IP,handshake:PORT,notify:PORT' format"
        ) from e
    return host, handshake_port, notify_port


def get_peer_zmq_from_request_id(request_id: str, is_producer: bool) -> str | None:
    """Extract the *peer's* zmq_address from the vLLM router request_id.

    The producer (prefill) needs the decode's address; the consumer (decode)
    needs the prefill's address.

    Returns ``None`` when the request_id does not encode peer info. The
    llm-d routing sidecar (``llm-d-inference-scheduler``) does not embed
    addresses in ``request_id``; instead it passes ``remote_host``,
    ``remote_handshake_port`` and ``remote_notify_port`` explicitly in
    ``kv_transfer_params``. Callers must handle the ``None`` return by
    falling back to those fields. See ``add_new_req`` for the canonical

View on GitHub (pinned to c794754062)

Solutions

  1. Log the offending zmq_address and diff it against 'host:IP,handshake:PORT,notify:PORT' (all three keys, integer ports)
  2. Fix the producer side (router or prefill instance) that generates/embeds the address so it emits all three segments correctly
  3. If the address is supplied via kv_transfer_params, correct remote_host / remote_handshake_port / remote_notify_port there
  4. Add a format check where the address is produced so malformed values fail at the source instead of at transfer time

Example fix

// before
zmq_address = "tcp://10.0.0.5:5555"  # wrong format, raises on parse
host, hs, nf = parse_moriio_zmq_address(zmq_address)

// after
zmq_address = "host:10.0.0.5,handshake:5555,notify:5556"
host, hs, nf = parse_moriio_zmq_address(zmq_address)
Defensive patterns

Strategy: validation

Validate before calling

import re

ZMQ_ADDR_RE = re.compile(
    r"^\s*host\s*:\s*[^,\s]+\s*,\s*handshake\s*:\s*\d+\s*,\s*notify\s*:\s*\d+\s*$"
)

def is_valid_moriio_zmq_address(addr: str) -> bool:
    return bool(ZMQ_ADDR_RE.match(addr))

# before parsing:
assert is_valid_moriio_zmq_address(zmq_address), f"bad zmq_address {zmq_address!r}"

Type guard

def is_moriio_zmq_address(v) -> bool:
    if not isinstance(v, str):
        return False
    parts = dict(s.partition(":")[::2] for s in v.split(","))
    try:
        return bool(parts.get("host", "").strip()) and int(parts["handshake"]) > 0 and int(parts["notify"]) > 0
    except (KeyError, ValueError):
        return False

Try / catch

try:
    host, hs_port, nf_port = parse_moriio_zmq_address(zmq_address)
except ValueError as e:
    raise ValueError(f"routing config error for request {request_id}: {e}") from e

Prevention

When it happens

Trigger: parse_moriio_zmq_address (directly, or via get_peer_zmq_from_request_id / build paths in moriio_common.py) is called with a string that omits one of the three key:value segments, has an empty host, or has non-integer ports. Common trigger: the router embeds a differently-formatted address into the request_id and the peer side parses it.

Common situations: Custom or older router versions that embed a different request_id format; passing a raw ZMQ endpoint like 'tcp://10.0.0.1:5555' instead of the key:value pair format; hand-typed kv_transfer_params with typos or missing ports; extra segments that overwrite expected keys.

Understand the failure class

Related errors


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