Skip to content

Tick Loop

The VLM runs on a fixed-frequency timer (default 2 Hz = 500 ms per tick). This decouples the model's processing rate from the sensor publishing rates (10-200 Hz).

Why 2 Hz?

Constraint Value
Camera topic rate ~10 Hz
Perception sidecar detect+segment ~200-400 ms per tick
Available budget per tick 500 ms
Overhead (snapshot + publish) ~5 ms

2 Hz gives the model enough time to complete inference within one tick while still being responsive to environmental changes.

LatestCache

LatestCache is a thread-safe buffer that stores the most recent message from each sensor topic:

class LatestCache:
    def snapshot(self, tick_id: int, tick_time: Stamp) -> VLMInput:
        """Atomically capture current state of all slots."""

ROS subscriber callbacks run on separate threads and overwrite slots continuously. The snapshot() method takes a lock and copies all current values into a single VLMInput.

Tick execution flow

def tick():
    # 1. Capture current sensor state
    snapshot = cache.snapshot(tick_id, timestamp)

    # 2. Detect new question → reset responder
    if snapshot.question != last_question:
        responder.reset()
        last_question = snapshot.question

    # 3. Run model inference
    output = responder.respond(snapshot)

    # 4. Publish response (if any)
    if output is not None:
        publisher.publish(output)

    # 5. Check if question is answered
    if responder.is_done():
        cache.clear_question()
        responder.reset()

Configuring tick rate

XIAO_HEI_VLM_TICK_HZ=2.0   # default
XIAO_HEI_VLM_TICK_HZ=1.0   # slower, for debugging
XIAO_HEI_VLM_TICK_HZ=5.0   # faster, if model is fast enough

Warning

Setting tick rate higher than the model can handle will cause ticks to overlap. The responder is synchronous — a slow inference blocks the next tick.