smoke.py 4.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. #!/usr/bin/env python3
  2. """Functional smoke test + single-stream decode speed for the MiMo endpoint.
  3. Stdlib only. Usage: smoke.py [--base http://127.0.0.1:30000/v1] [--out results.json]
  4. """
  5. import argparse, base64, json, struct, sys, time, urllib.request, zlib
  6. p = argparse.ArgumentParser()
  7. p.add_argument("--base", default="http://127.0.0.1:30000/v1")
  8. p.add_argument("--model", default="mimo-v2.6-pro")
  9. p.add_argument("--out")
  10. a = p.parse_args()
  11. def post(path, body, stream=False):
  12. req = urllib.request.Request(a.base + path, json.dumps(body).encode(), {"Content-Type": "application/json"})
  13. return urllib.request.urlopen(req, timeout=900) if stream else json.load(urllib.request.urlopen(req, timeout=900))
  14. def chat(messages, **kw):
  15. body = {"model": a.model, "messages": messages, "temperature": 0, "max_tokens": 512, **kw}
  16. return post("/chat/completions", body)["choices"][0]
  17. def png(w, h, rgb_fn):
  18. raw = b"".join(b"\0" + bytes(c for x in range(w) for c in rgb_fn(x, y)) for y in range(h))
  19. chunk = lambda t, d: struct.pack(">I", len(d)) + t + d + struct.pack(">I", zlib.crc32(t + d) & 0xFFFFFFFF)
  20. return b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0)) + chunk(b"IDAT", zlib.compress(raw)) + chunk(b"IEND", b"")
  21. def stream_speed(prompt, max_tokens, thinking=False):
  22. body = {"model": a.model, "messages": [{"role": "user", "content": prompt}], "temperature": 0,
  23. "max_tokens": max_tokens, "stream": True, "stream_options": {"include_usage": True},
  24. "chat_template_kwargs": {"enable_thinking": thinking}}
  25. t0 = time.time(); first = None; usage = None; text = []
  26. for line in post("/chat/completions", body, stream=True):
  27. line = line.decode().strip()
  28. if not line.startswith("data:") or line == "data: [DONE]":
  29. continue
  30. ev = json.loads(line[5:])
  31. if ev.get("usage"):
  32. usage = ev["usage"]
  33. for ch in ev.get("choices", []):
  34. d = ch.get("delta", {})
  35. piece = (d.get("content") or "") + (d.get("reasoning_content") or "")
  36. if piece:
  37. first = first or time.time(); text.append(piece)
  38. end = time.time(); n = usage["completion_tokens"]
  39. return {"completion_tokens": n, "ttft_s": round(first - t0, 3),
  40. "decode_tok_s": round((n - 1) / (end - first), 2), "text_head": "".join(text)[:160]}
  41. R = {}
  42. c = chat([{"role": "user", "content": "What is 19 + 23? Answer with just the number."}],
  43. chat_template_kwargs={"enable_thinking": False})
  44. R["arithmetic"] = {"ok": "42" in c["message"]["content"], "content": c["message"]["content"]}
  45. c = chat([{"role": "user", "content": "How many r letters are in 'strawberry'? Think, then answer."}], max_tokens=2048)
  46. m = c["message"]
  47. R["reasoning"] = {"ok": bool(m.get("reasoning_content")) and "3" in (m.get("content") or ""),
  48. "reasoning_chars": len(m.get("reasoning_content") or ""), "content": (m.get("content") or "")[:200]}
  49. tools = [{"type": "function", "function": {"name": "get_weather", "description": "Get current weather for a city",
  50. "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}}]
  51. c = chat([{"role": "user", "content": "What's the weather in Cardiff right now?"}], tools=tools,
  52. chat_template_kwargs={"enable_thinking": False})
  53. tc = c["message"].get("tool_calls") or []
  54. R["tool_call"] = {"ok": bool(tc) and tc[0]["function"]["name"] == "get_weather" and "Cardiff" in tc[0]["function"]["arguments"],
  55. "tool_calls": tc, "finish_reason": c["finish_reason"]}
  56. # Left half red, right half blue.
  57. img = base64.b64encode(png(256, 128, lambda x, y: (220, 20, 20) if x < 128 else (20, 40, 220))).decode()
  58. c = chat([{"role": "user", "content": [
  59. {"type": "image_url", "image_url": {"url": "data:image/png;base64," + img}},
  60. {"type": "text", "text": "This image has two halves. What colour is the left half and what colour is the right half? Answer briefly."}]}],
  61. chat_template_kwargs={"enable_thinking": False})
  62. t = c["message"]["content"].lower()
  63. R["image"] = {"ok": "red" in t and "blue" in t and t.find("red") < t.find("blue"), "content": c["message"]["content"]}
  64. R["speed_code"] = stream_speed("Write a Python function implementing an LRU cache class with get/put, with type hints and docstrings. Code only.", 256)
  65. R["speed_prose"] = stream_speed("Write a short story about a lighthouse keeper who finds a message in a bottle.", 256)
  66. print(json.dumps(R, indent=2))
  67. if a.out:
  68. json.dump(R, open(a.out, "w"), indent=2)
  69. sys.exit(0 if all(v.get("ok", True) for v in R.values()) else 1)