onw / trace_experts.py
ryugyosoft's picture
v0.6.0: stream MoE experts from the SSD (LRU cache, direct I/O) when the model does not fit; expert memory setting
3a7bf50 verified
Raw History Blame Contribute Delete
3.97 kB
"""Record which experts a MoE onw model picks, per layer and per step, over a set of realistic chats (text, code,
image, multi-turn). Writes a JSON trace that sim_expert_cache.py replays against expert caches of different sizes.
usage: python trace_experts.py MODEL_DIR OUT.json [IMAGE]"""
import json, sys, time
from PIL import Image
from onw.chat import ChatEngine
from onw.runtime import SegmentedModel
TRACE = [] # one entry per _bind_experts call: [layer, S, n, [experts...]]
_orig = SegmentedModel._bind_experts
def _spy(self, req, m, idx, w, S, n):
TRACE.append([m["consume_moe"], S, n, sorted({int(e) for e in idx[:n].reshape(-1)})])
return _orig(self, req, m, idx, w, S, n)
SegmentedModel._bind_experts = _spy
CODE = '''def load_config(path):
with open(path) as f:
data = json.load(f)
if "name" not in data:
raise ValueError("missing name")
data["port"] = int(data.get("port", 8080))
return data'''
CHATS = [
("chat-ja", [["NPUとGPUの違いを、身近なたとえを使って説明してください。"],
["では、ノートPCでLLMを動かすならどちらが向いていますか?理由も。"]]),
("code", [[f"次のPython関数に型ヒントとdocstringを付けて、関数全体を書き直してください。\n\n```python\n{CODE}\n```"]]),
("summary-ja", [["次の文章を3文で要約してください。\n\n" + "東京は日本の首都であり、政治・経済・文化の中心地である。"
"江戸時代には徳川幕府が置かれ、1868年の明治維新で東京と改称された。現在は約1400万人が暮らす世界有数の大都市で、"
"多くの企業や大学が集まっている。一方で、人口集中による住宅価格の高騰や通勤の混雑、災害への備えといった課題も抱えている。" * 2]]),
("chat-en", [["Explain the difference between a process and a thread, with a short Python example."]]),
("math-ja", [["ある商品を定価の2割引きで買うと960円でした。定価はいくらですか?途中の式も書いてください。"]]),
]
def main():
d, out = sys.argv[1], sys.argv[2]
img = Image.open(sys.argv[3]) if len(sys.argv) > 3 else None
e = ChatEngine(d, "NPU", pld=False)
runs = []
chats = list(CHATS)
if img is not None:
chats.insert(1, ("image", [[{"type": "image", "image": img}, {"type": "text", "text": "この画像について詳しく説明してください。"}],
["その動物の気持ちを想像して、短い物語にしてください。"]]))
for name, turns in chats:
e.checkpoint = None
msgs = []
for t in turns:
content = t[0] if len(t) == 1 and isinstance(t[0], str) else t
msgs.append({"role": "user", "content": content})
start = len(TRACE)
parts, st, t0 = [], None, time.time()
for x in e.stream_chat([dict(m) for m in msgs], 200):
if isinstance(x, dict):
st = x
else:
parts.append(x)
text = "".join(parts)
msgs.append({"role": "assistant", "content": text})
runs.append({"name": name, "start": start, "end": len(TRACE), "prompt_tokens": st["prompt_tokens"],
"cached": st["cached_tokens"], "completion": st["completion_tokens"]})
print(f"{name}: prompt {st['prompt_tokens']} (reused {st['cached_tokens']}), {st['completion_tokens']} tokens, "
f"{time.time() - t0:.1f}s | {text[:50]!r}", flush=True)
meta = {k: e.model.meta.get(k) for k in ("arch",)}
json.dump({"E": e.model.E, "K": e.model.K, "meta": meta, "runs": runs, "trace": TRACE}, open(out, "w"))
print(f"wrote {len(TRACE)} binds to {out}")
if __name__ == "__main__":
main()