rootxhacker commited on
Commit
bd74813
·
verified ·
1 Parent(s): 9152c09

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. app.py +77 -0
  2. hobbylm/model.py +6 -2
  3. hobbylm/moe.py +3 -0
  4. requirements.txt +1 -0
app.py CHANGED
@@ -318,6 +318,68 @@ def generate_image_fn(prompt, negative, steps, guidance, seed, progress=gr.Progr
318
  threading.Thread(target=_warmup, daemon=True).start()
319
 
320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  # --------------------------------------------------------------------------- UI
322
 
323
  INTRO = """# 🪶 HobbyLM Playground
@@ -379,5 +441,20 @@ with gr.Blocks(title="HobbyLM Playground", theme=gr.themes.Soft()) as demo:
379
  ["a bowl of fresh strawberries, studio food photography", NEG_DEFAULT, 60, 5.0, 42]],
380
  [g_prompt, g_neg, g_steps, g_cfg, g_seed], cache_examples=False)
381
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
  if __name__ == "__main__":
383
  demo.queue(max_size=20).launch()
 
318
  threading.Thread(target=_warmup, daemon=True).start()
319
 
320
 
321
+ # --------------------------------------------------------------------------- how it works (MoE routing)
322
+
323
+ @spaces.GPU(duration=90)
324
+ def how_it_works(prompt, layer):
325
+ import matplotlib
326
+ matplotlib.use("Agg")
327
+ import matplotlib.pyplot as plt
328
+ import numpy as np
329
+ dev = _device()
330
+ enc = _enc()
331
+ model, cfg = _load_llm("HobbyLM-Base")
332
+ model.to(dev)
333
+ try:
334
+ ids = enc.encode_ordinary(prompt or "The quick brown fox jumps over the lazy dog.")[:40]
335
+ if not ids:
336
+ ids = enc.encode_ordinary("Hello world")
337
+ toks = torch.tensor([ids], device=dev)
338
+ with torch.no_grad():
339
+ model(toks) # populates last_topi on each MoE block
340
+ ne, S = cfg.n_experts, len(ids)
341
+ moe_layers = [i for i, b in enumerate(model.blocks) if getattr(b, "is_moe", False)]
342
+ layer = min(max(int(layer), moe_layers[0]), moe_layers[-1])
343
+ blk = model.blocks[layer]
344
+ topi = blk.ffn.last_topi.reshape(S, -1).cpu().numpy()
345
+ topv = blk.ffn.last_topv.reshape(S, -1).cpu().float().numpy()
346
+ labels = [repr(enc.decode([i]))[1:-1][:12] for i in ids]
347
+
348
+ # (1) per-token routing heatmap at the chosen layer
349
+ M = np.zeros((S, ne))
350
+ for s in range(S):
351
+ for j in range(topi.shape[1]):
352
+ M[s, int(topi[s, j])] = topv[s, j]
353
+ fig1, ax = plt.subplots(figsize=(11, max(2.5, S * 0.32)))
354
+ im = ax.imshow(M, aspect="auto", cmap="magma")
355
+ ax.set_yticks(range(S)); ax.set_yticklabels(labels, fontsize=8)
356
+ ax.set_xlabel(f"expert (0–{ne - 1})"); ax.set_ylabel("token")
357
+ ax.set_title(f"Layer {layer}: each token routes to its top-{cfg.top_k} of {ne} experts (+1 shared, always on)")
358
+ fig1.colorbar(im, ax=ax, label="gate weight", fraction=0.025)
359
+ fig1.tight_layout()
360
+
361
+ # (2) expert load across ALL MoE layers (the load-balancing story)
362
+ load = np.zeros(ne)
363
+ for i in moe_layers:
364
+ for e in model.blocks[i].ffn.last_topi.reshape(-1).cpu().numpy():
365
+ load[int(e)] += 1
366
+ fig2, ax2 = plt.subplots(figsize=(11, 2.6))
367
+ ax2.bar(range(ne), load, color="#7c3aed")
368
+ ax2.set_xlabel("expert"); ax2.set_ylabel("tokens routed")
369
+ ax2.set_title(f"Expert load over all {len(moe_layers)} MoE layers — fairly even = aux-loss-free balancing working")
370
+ fig2.tight_layout()
371
+
372
+ active = cfg.top_k + cfg.n_shared
373
+ summary = (f"**{S} tokens** · **{ne} experts/layer**, top-{cfg.top_k} routed + {cfg.n_shared} shared. "
374
+ f"At each of the {len(moe_layers)} MoE layers every token uses only **{active}/{ne + cfg.n_shared} "
375
+ f"experts** → that's the *sparse* in sparse-MoE: a 500M model that computes like a far smaller one "
376
+ f"per token. Different tokens pick different experts (the heatmap); across the whole prompt the load "
377
+ f"spreads fairly evenly (the bar chart).")
378
+ return fig1, fig2, summary
379
+ finally:
380
+ model.to("cpu")
381
+
382
+
383
  # --------------------------------------------------------------------------- UI
384
 
385
  INTRO = """# 🪶 HobbyLM Playground
 
441
  ["a bowl of fresh strawberries, studio food photography", NEG_DEFAULT, 60, 5.0, 42]],
442
  [g_prompt, g_neg, g_steps, g_cfg, g_seed], cache_examples=False)
443
 
444
+ with gr.Tab("🔬 How it works"):
445
+ gr.Markdown(
446
+ "HobbyLM is a **sparse Mixture-of-Experts**: each MoE layer holds **36 little expert networks**, "
447
+ "but a router sends every token through only its **top-6** (plus 1 always-on shared expert). "
448
+ "So a 500M model does the *compute* of a much smaller one per token. Type some text and watch the "
449
+ "router decide — which experts each token uses, and how the load spreads across all 36.")
450
+ with gr.Row():
451
+ hiw_prompt = gr.Textbox(label="Text", value="The capital of France is Paris, a beautiful city.", scale=4)
452
+ hiw_layer = gr.Slider(1, 15, value=8, step=1, label="MoE layer", scale=1)
453
+ hiw_btn = gr.Button("Visualize routing", variant="primary")
454
+ hiw_summary = gr.Markdown()
455
+ hiw_heat = gr.Plot(label="Per-token expert routing")
456
+ hiw_load = gr.Plot(label="Expert load (balancing)")
457
+ hiw_btn.click(how_it_works, [hiw_prompt, hiw_layer], [hiw_heat, hiw_load, hiw_summary])
458
+
459
  if __name__ == "__main__":
460
  demo.queue(max_size=20).launch()
hobbylm/model.py CHANGED
@@ -320,7 +320,8 @@ class MoETransformer(nn.Module):
320
  return self._rope_cache[key]
321
 
322
  def forward(self, idx: Tensor | None = None, targets: Tensor | None = None,
323
- inputs_embeds: Tensor | None = None, p_mask: Tensor | None = None):
 
324
  # accept either token ids OR precomputed embeddings (inputs_embeds), for multimodal splicing.
325
  if inputs_embeds is None:
326
  x = self.embed(idx)
@@ -333,9 +334,12 @@ class MoETransformer(nn.Module):
333
  B, S = x.shape[0], x.shape[1]
334
  cos, sin = self.rope(S, device, x.dtype)
335
  aux_sum = x.new_zeros(())
336
- for blk in self.blocks:
337
  x, aux = blk(x, cos, sin)
338
  aux_sum = aux_sum + aux
 
 
 
339
  x = self.final_norm(x)
340
  cfg = self.cfg
341
  sc = cfg.logit_softcap
 
320
  return self._rope_cache[key]
321
 
322
  def forward(self, idx: Tensor | None = None, targets: Tensor | None = None,
323
+ inputs_embeds: Tensor | None = None, p_mask: Tensor | None = None,
324
+ capture_layer: int | None = None):
325
  # accept either token ids OR precomputed embeddings (inputs_embeds), for multimodal splicing.
326
  if inputs_embeds is None:
327
  x = self.embed(idx)
 
334
  B, S = x.shape[0], x.shape[1]
335
  cos, sin = self.rope(S, device, x.dtype)
336
  aux_sum = x.new_zeros(())
337
+ for i, blk in enumerate(self.blocks):
338
  x, aux = blk(x, cos, sin)
339
  aux_sum = aux_sum + aux
340
+ # interp hook: return the residual stream after block `capture_layer` (and skip the rest)
341
+ if capture_layer is not None and i == capture_layer:
342
+ return x
343
  x = self.final_norm(x)
344
  cfg = self.cfg
345
  sc = cfg.logit_softcap
hobbylm/moe.py CHANGED
@@ -145,6 +145,9 @@ class MoE(nn.Module):
145
  B, S, d = x.shape
146
  xf = x.reshape(-1, d)
147
  topi, topv, aux = self._route(xf)
 
 
 
148
  if self.backend == "grouped":
149
  out = self._experts_grouped(xf, topi, topv)
150
  else:
 
145
  B, S, d = x.shape
146
  xf = x.reshape(-1, d)
147
  topi, topv, aux = self._route(xf)
148
+ # interp capture: remember this layer's per-token expert selection (top-k indices + gate weights)
149
+ self.last_topi = topi.detach() # (T=B*S, k)
150
+ self.last_topv = topv.detach() # (T=B*S, k)
151
  if self.backend == "grouped":
152
  out = self._experts_grouped(xf, topi, topv)
153
  else:
requirements.txt CHANGED
@@ -8,3 +8,4 @@ tiktoken
8
  pillow
9
  numpy
10
  sentencepiece
 
 
8
  pillow
9
  numpy
10
  sentencepiece
11
+ matplotlib