UI-TARS on Apple Silicon
ByteDance's vision-grounded UI automation agent. UI-TARS reads screen
pixels and emits click / type / scroll actions — the operator side
of a computer-use loop. rapid-mlx ships nine MLX quants across the
1.5-7B, 7B-SFT, 7B-DPO, and 72B-DPO variants, with the custom
ui_tars tool parser wired into the alias config.
pip install rapid-mlx && rapid-mlx serve ui-tars-1.5-7b-4bit
runs the latest 1.5 SFT checkpoint at 4-bit on any 16 GB+ Apple
Silicon Mac. The ui_tars parser is auto-selected, so
the OpenAI /v1/chat/completions endpoint emits clean
tool_calls for every screen action. Headline tok/s:
TODO — bench pending on M3 Ultra.
Why UI-TARS matters
Computer-use agents are the niche where a small open VLM serving on a laptop is dramatically more useful than a cloud API: latency, privacy of the screen, and the long tail of "this app, on this OS, with these accessibility settings" all bias toward local execution. UI-TARS is the strongest open competitor in that niche — better grounding than generic VLMs because it was post-trained on a large screen-action dataset. The 1.5-7B is small enough to live next to whatever it is operating; the 72B-DPO is for the rare case where a single workstation drives a fleet of remote desktops.
Engineering notes — what we shipped
UI-TARS was the case study for our streaming-parser hardening
epic — twelve commits between r5-B and r10-F
landed across PRs #818, #823, #829, #833, #834, and #850. The
thread of bugs and fixes:
-
r5-B (PR #823, 6712bce6) — UI-TARS expects a tool-coupled
system prompt to gate the
Thought: / Action: ...trace. Without the right sysprompt the model would emit prose instead of structured actions. Fix: auto-prepend the parser-specific sysprompt whentools=is set, and add chat/responses lane parity so thecomputer_use_previewalias produces the sametool_callsoutput as/v1/chat/completions. -
r6-B (PRs #829 / #833, ee380b1c..a16d8c81) — the streaming
parser was emitting partial
Action:coordinates before the full screen-coord block had buffered. Downstream clients executed half-clicks. Fix: introduce trailing-prefix hold-back: the parser only flushes once the coordinate token has settled, and theThought: EOSpath is narrowed so plain-chat responses don't accidentally hit the action path. -
r7-A (PR #834, 92bc9083) — streaming reasoning + coord
parity across chat and responses lanes, plus the
computer_use_previewalias. -
r10-F (PR #850, 9006bb4d) — accumulator-anchor fix +
enable_thinking=falsehonored, so callers can ask for plain Action output without reasoning prose.
These fixes are why rapid-mlx serve ui-tars-1.5-7b-4bit
works end-to-end on day one. The parser source lives at
vllm_mlx/tool_parsers/ui_tars_tool_parser.py
and the streaming reasoning parser at
vllm_mlx/reasoning/ui_tars_parser.py.
Upstream status
Upstream mlx-vlm can load the UI-TARS weights but does
not understand the action grammar — it emits the raw
Thought: … Action: click(x,y) string as
chat content. rapid-mlx converts that into
tool_calls with a strongly-typed JSON arguments
payload, which is what downstream operator frameworks (browser
controllers, OSWorld harnesses) actually consume.
Pick your quant
All nine quants serve through rapid-mlx serve <alias> with no extra flags.
| Alias | Bits | Approx. disk | Recommended for | HF repo |
|---|---|---|---|---|
ui-tars-1.5-7b-4bit | 4 | ~4.0 GB | Default — latest 1.5 SFT, fits 16 GB Mac | UI-TARS-1.5-7B-4bit |
ui-tars-1.5-7b-6bit | 6 | ~5.5 GB | Quality bump on 24 GB+ | UI-TARS-1.5-7B-6bit |
ui-tars-1.5-7b-8bit | 8 | ~7.5 GB | Lossless tier for eval runs | UI-TARS-1.5-7B-8bit |
ui-tars-7b-sft-4bit | 4 | ~4.0 GB | Original 7B SFT — broadest task coverage | UI-TARS-7B-SFT-4bit |
ui-tars-7b-sft-8bit | 8 | ~7.5 GB | Original 7B SFT, lossless tier | UI-TARS-7B-SFT-8bit |
ui-tars-7b-dpo-4bit | 4 | ~4.0 GB | DPO-aligned 7B — better instruction following | UI-TARS-7B-DPO-4bit |
ui-tars-7b-dpo-6bit | 6 | ~5.5 GB | DPO 7B quality bump | UI-TARS-7B-DPO-6bit |
ui-tars-7b-dpo-8bit | 8 | ~7.5 GB | DPO 7B lossless tier | UI-TARS-7B-DPO-8bit |
ui-tars-72b-dpo-4bit | 4 | ~40 GB | M3 Ultra workstation — best accuracy | UI-TARS-72B-DPO-4bit |
Recommended settings
UI-TARS is sensitive to sampling. The defaults baked into the alias
config are temperature=0.0, top_p=1.0 —
deterministic, because operator agents that take physical-world
actions on the user's machine must be reproducible. Override only
if you're running an exploration / data-collection workload.
rapid-mlx serve ui-tars-1.5-7b-4bit \
--port 8000 \
--host 127.0.0.1
The system prompt is auto-prepended when the request includes
tools=. If you supply your own sysprompt, the parser
will still produce tool_calls as long as the
Thought: / Action: grammar is present
in the model's output.
Tutorial — cURL with a screen-action tool
curl http://127.0.0.1:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "ui-tars-1.5-7b-4bit",
"messages": [
{"role": "user", "content": [
{"type": "text", "text": "Click the Submit button."},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,<screenshot>"}}
]}
],
"tools": [
{"type": "function", "function": {
"name": "click",
"description": "Click at screen coordinates",
"parameters": {
"type": "object",
"properties": {"x": {"type": "number"}, "y": {"type": "number"}},
"required": ["x", "y"]
}
}}
]
}'
Python via the OpenAI client:
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="none")
resp = client.chat.completions.create(
model="ui-tars-1.5-7b-4bit",
messages=[
{"role": "user", "content": [
{"type": "text", "text": "Click the Submit button."},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{screenshot_b64}"}},
]},
],
tools=[{
"type": "function",
"function": {
"name": "click",
"description": "Click at screen coordinates",
"parameters": {
"type": "object",
"properties": {"x": {"type": "number"}, "y": {"type": "number"}},
"required": ["x", "y"],
},
},
}],
)
for call in resp.choices[0].message.tool_calls:
print(call.function.name, call.function.arguments)
Performance
TODO — M-series headline tok/s pending. We'll publish 1.5-7B-4bit and 72B-DPO-4bit numbers on M3 Ultra alongside the next release.
Known limitations
- UI-TARS expects a single screenshot per turn. Multi-image context (before/after) works but the grounding accuracy drops meaningfully — feed the current frame only.
-
enable_thinking=trueon the 7B-SFT variant emits a verboseThought:trace that consumes the context budget. Setenable_thinking=falsefor headless loops where you only need the action. - The 72B-DPO-4bit is borderline on a 64 GB Mac — it works but leaves no headroom for a long context or another process. 96 GB unified memory is the comfortable floor.