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 Download trace_experts.py from ryugyosoft/onw: direct link, hf CLI and curl.
- Browser
- Download file 3.97 kB
-
https://huggingface.co/ryugyosoft/onw/resolve/main/trace_experts.py
- Command line
-
hf download hf://ryugyosoft/onw/trace_experts.py
-
curl -L -o trace_experts.py https://huggingface.co/ryugyosoft/onw/resolve/main/trace_experts.py
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() | |