Views
No views yet
oQ8) MLX build of Hcompany/Holo-3.1-35B-A3B — H Company's GUI-grounding / computer-use VLM — with a Multi-Token-Prediction (MTP / nextn) head grafted in so oMLX can run native speculative decoding.symrex/Holo-3.1-35B-A3B-oQ8 (which dropped the MTP head) with the 8-bit MTP module transplanted from tfjack/Qwen3.6-35B-A3B-oQ8-fp16-mtp — the same Qwen3.6-35B-A3B base Holo-3.1 was fine-tuned from.Qwen3.6-35B-A3B base. Both models share a byte-identical non-MTP tensor set (2010 tensors, model_type: qwen3_5_moe, hidden 2048, 40 layers, 256 experts, vocab 248320), so the 42-tensor language_model.mtp.* block (already 8-bit, group_size matched) drops in with no transforms. Config gains text_config.mtp_num_hidden_layers=1, mtp_use_dedicated_embeddings=False, and 6 MTP quant-overrides.| base oQ8 | this (oQ8-mtp) | |
|---|---|---|
| Decode (300 tok) | 55.0 tok/s | 60.5 tok/s (~1.1×) |
| MTP acceptance (structured text) | — | 94.8% (145/153) |
| Output vs base | — | byte-identical (lossless) |
{x,y} blobs; for pure grounding, screenshot prefill dominates latency and MTP barely moves it — it pays off on longer agentic/navigation traces.)temperature=0):
[0,1000] JSON {x,y} (not Holo1's pixel Click(x,y)). Use greedy + thinking off; greedy is both the correct setting for stable coordinates and what maximizes MTP acceptance.1from openai import OpenAI
2client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="...")
3
4W, H = 1280, 720 # send a smart_resize'd image; scale coords against THESE dims
5prompt = (
6 "Localize an element on the GUI image according to the provided target "
7 "and output a click position.\n"
8 " * You must output a valid JSON following the format: "
9 '{"properties":{"x":{"type":"integer"},"y":{"type":"integer"}},"required":["x","y"]}\n'
10 " Your target is:\nthe 'Save Changes' button")
11
12r = client.chat.completions.create(
13 model="Holo-3.1-35B-A3B-oQ8-mtp", temperature=0,
14 messages=[{"role": "user", "content": [
15 {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}},
16 {"type": "text", "text": prompt}]}],
17 extra_body={
18 "chat_template_kwargs": {"enable_thinking": False},
19 "response_format": {"type": "json_schema", "json_schema": {"name": "point",
20 "schema": {"type": "object", "required": ["x", "y"], "additionalProperties": False,
21 "properties": {"x": {"type": "integer"}, "y": {"type": "integer"}}}}},
22 })
23import json
24p = json.loads(r.choices[0].message.content)
25px, py = int(p["x"]/1000*W), int(p["y"]/1000*H) # -> pixel click pointsmart_resize the screenshot first (Qwen factor = patch 16 × merge 2 = 32; min 65 536 / max 16.7 M px) so the server's internal resize doesn't shift coordinates, and scale against the dimensions you actually send.
| Profile | Grounding accuracy | Use for |
|---|---|---|
greedy (temperature=0, no penalties) | 12/12 hit, median 3 px | grounding / localization |
instruct (t0.7, top_p0.8, top_k20, min_p0, presence_penalty1.5) | 11/12, median 27 px, jitters ±40 px | agentic / navigation / general non-thinking |
temperature=0, no penalties. Deterministic, precise coordinates; this is also H Company's official localization setting. presence_penalty on a 2-integer {x,y} actively skews coordinates — leave penalties off. MTP acceptance is lower here (~33–60%) but irrelevant: outputs are ~15 tokens, prefill dominates, so greedy costs no wall-clock.temperature=0.7, top_p=0.8, top_k=20, min_p=0, presence_penalty=1.5, repetition_penalty=1.0 (Qwen3 anti-loop config). This is where MTP pays off (long traces) and where acceptance climbs >90% — temperature softens the rejection-sampling threshold so near-miss drafts are accepted.generation_config.json ships the instruct sampling as the default (temp 0.7 / top_p 0.8 / top_k 20 / min_p 0); pass temperature=0 explicitly for grounding. presence_penalty is a serving/API param (not in generation_config.json) — add it only in agentic mode.mtp_num_hidden_layers; the log prints MTP path activated ... accept=N/M.