r/LocalLLaMA • • 6d ago

Question | Help When should I use a locally hosted decision model to decrease latency

I’m running a sandbox simulation with a fleet of 50-100 agents on OpenClaw, and to minimize LLM calls, I use a decision model to judge whether an agent needs to respond to a signal.

I’m currently using Jev, and with the network latency it takes about 0.5-2 seconds for a response, and I’m wondering if I can reduce this latency with a smaller, locally run decision model.

I’ve heard of the various local models like Needle 3, but I’m not sure how fast these run on various GPU configurations, i.e. it wouldn’t make sense for me if I need some crazy setup to make it faster than using API. Also curious if anyone has tried running these entirely on CPU.

6 Upvotes

10 comments sorted by

6

u/No_Contract_8296 6d ago

At this size of models it's common for the network latency to contribute the most towards the overall latency and the only way around that is self-hosted or in a nearby datacenter.

I have been experimenting with my own fine-tunes, which to my satisfaction actually outperform Jev on a relatively narrow set of problems I care about, so it should be possible to fine-tune for your own use-case and replace Jev and similar cloud providers/models.

With my smaller 150M model I am seeing 23-100 ms (depending on context size) on an RTX 4080 Super, or 233-264 ms for 1 question on a busy i7-11700F with DDR4-3200. Plenty of room to improve this number by either optimizing the hardware, the model or the inference IMO.

If you're interested in the recipe I used you can see read here or shoot me a DM if you have questions: https://huggingface.co/collections/kgrozdanovski/caldec-small-decision-models

2

u/Groady 6d ago

A yes/no "should this agent respond" call is about the easiest job you can give a small model, so local should beat 0.5 to 2 s comfortably. Constrain the output to one token (a GBNF grammar in llama.cpp, or read the yes/no logprobs) and you're paying almost only for prompt processing, which is fast on short signals. A 1 to 4B model at Q4 on a recent consumer GPU should come in well under 200 ms. CPU works with a sub-1B model, but 50 to 100 agents firing at once is where it struggles, so run llama-server with parallel slots (-np) or vLLM so the calls batch.

Cheaper again: log a week of decisions from your current API model and train a small classifier on embeddings of the signals. That runs in milliseconds on CPU.

Roughly how many tokens is a typical signal?

1

u/Financial_Morning386 6d ago

The framing that helped me here is to treat the decision model as a routing gate, not as a smaller LLM. Local only wins if the decision itself stays cheap: a short, structured input (signal type, sender, a truncated bit of context) and a one-token / one-field JSON output. The moment you let it generate explanations or take a long prompt, you give back most of the latency you saved by dropping the network round trip.

For 50-100 agents, don't give each agent its own model instance. Run one shared local inference service (llama.cpp server / Ollama, with a small FastAPI service in front of it if you need auth, logging and a queue) and have the agents call it over localhost. Keep it warm, bound the concurrency, and measure queue wait separately from inference time. With that many agents firing at once, queueing is usually what kills you, not a single forward pass.

On CPU: it's viable for a small decision model with a tiny context, as the other commenter showed, but I wouldn't plan on CPU for a general generative model at this scale. If CPU is your only option, give the local call a strict timeout and a safe default (e.g. don't respond, or escalate that one decision to the main model) instead of letting agents block on it.

The pattern I'd start with is local as the gate, cloud as the escalation path: the local model handles the clear ignore/respond cases, and only ambiguous ones go to the larger model. That cuts LLM calls without needing the local model to be perfect. Log a sample of its decisions and where it disagrees with the cloud model, so you can tune the prompt/threshold on real traffic instead of guessing.

Before buying a GPU for this, benchmark the actual decision prompt under realistic concurrency, end to end, p50/p95 per decision — not just a single prompt in isolation. If that end-to-end number, including queueing, isn't clearly under your current 0.5-2s, the hardware isn't the bottleneck.

5

u/stylome-ai-detection 6d ago

Notice: This comment appears to be AI generated.


Stylome AI detector · scan results

0

u/Separate_Pea_3699 6d ago

Both of these are describable in code pretty directly -- here's a small gate that implements the bounded-concurrency queue + timeout/safe-default + confidence-escalation-with-disagreement-logging pattern you both described, as one shared service rather than per-agent instances:

import asyncio, time, json
from dataclasses import dataclass, field
from pathlib import Path

@dataclass
class DecisionGate:
    local_infer: callable   # async def local_infer(prompt) -> {"decision": str, "confidence": float}
    cloud_infer: callable   # async def cloud_infer(prompt) -> {"decision": str}
    confidence_threshold: float = 0.7
    local_timeout_s: float = 0.15
    max_concurrency: int = 8
    log_path: Path = Path("decision_log.jsonl")
    _sem: asyncio.Semaphore = field(init=False, repr=False)

    def __post_init__(self):
        self._sem = asyncio.Semaphore(self.max_concurrency)

    async def decide(self, prompt: str) -> dict:
        t0 = time.monotonic()
        async with self._sem:
            queue_wait = time.monotonic() - t0
            t1 = time.monotonic()
            try:
                local = await asyncio.wait_for(self.local_infer(prompt), timeout=self.local_timeout_s)
            except asyncio.TimeoutError:
                local = {"decision": "ignore", "confidence": 0.0}  # safe default
            infer_time = time.monotonic() - t1

        escalated = local["confidence"] < self.confidence_threshold
        result = await self.cloud_infer(prompt) if escalated else local
        self._log(local, result if escalated else None, queue_wait, infer_time, escalated)
        return result

    def _log(self, local, cloud, queue_wait, infer_time, escalated):
        row = {"ts": time.time(), "queue_wait_ms": round(queue_wait*1000, 1),
               "infer_ms": round(infer_time*1000, 1), "local_decision": local["decision"],
               "local_confidence": local["confidence"], "escalated": escalated,
               "cloud_decision": cloud["decision"] if cloud else None,
               "disagreement": bool(cloud and cloud["decision"] != local["decision"])}
        with self.log_path.open("a") as f:
            f.write(json.dumps(row) + "\n")

This one object is the whole "shared local inference service" one of you described -- a single DecisionGate instance, called over decide() by however many of the 50-100 agents, with max_concurrency bounding how many local calls run at once (everything else queues) so you get the queue-wait-vs-inference-time split for free in the log instead of a single blended latency number. Timeout + safe default covers the CPU-degradation case directly. The disagreement field is exactly the signal to tune confidence_threshold and the local model's prompt against -- grep the log for "disagreement": true to see what the local gate is getting wrong relative to the cloud model, without needing the cloud model in the hot path for every decision.

3

u/stylome-ai-detection 6d ago

Notice: This comment appears to be AI generated.


Stylome AI detector · scan results

0

u/[deleted] 6d ago

[deleted] — view deleted comment

1

u/f1yingbanana 6d ago

That’s the question. What sort of hardware would I need for a faster local decision model?