class PreparedMoE:
def __init__(self, front, experts, tail, state, *, prefetch=None, layer_idx=None):
self.front, self.experts, self.tail, self.state = front, experts, tail, state
self.prefetch, self.layer_idx = prefetch, layer_idx
self.small_batch = None # optional small-batch chain
# Split batches whose routed experts launch beside the shared expert's
# gate/up half instead of after it.
self.join = batch_table(JOIN[state.tp])
self.sms = torch.cuda.get_device_properties(state.device).multi_processor_count
for b, sms in front.split.items(): # the split's down CTAs take the SMs left
experts.grids[b] = min(experts.grid_for(b), self.sms - sms)
# Batches whose experts prefetch the next layer's attention weights in-kernel
# (runtime.prefetch.EXPERTS_AHEAD): their (address, bytes) table.
self.ahead: dict[int, torch.Tensor] = {}
for b in state.batches:
_, expanded, buffers = state.views[b]
publish, planned = tail.publish.get(b, ()), front.planned(b)
twoshot = b in tail.twoshots
table = None if prefetch is None else prefetch.ahead(layer_idx, b)
args = (b, buffers, expanded, publish, planned, twoshot, True)
experts.prepare(*args, ahead=table is not None)
if table is not None and experts.ahead[b]:
self.ahead[b] = table
def run(self, x, out, *, consume=None):
state, b = self.state, x.shape[0]
if self.small_batch is not None and b in self.small_batch.batches:
return self.small_batch.run(out, consume)
main = torch.cuda.current_stream(state.device)
# The shared expert runs on the side stream beside the routed experts, or, when
# the persistent expert grid spans every SM, beside the front only. Split: its
# gate/up half beside the front, its down rows beside the experts.
before = self.experts.grid_for(b) >= self.sms
if self.front.merged_batch(b):
# One kernel publishes the routing logits and leaves h; the down rows then
# run beside the experts.
ids, weights, latent = self.front.route(x)
state.begin.record(main)
with torch.cuda.stream(state.stream):
state.stream.wait_event(state.begin)
shared, beside = self.front.shared_expert(x, self.tail)
beside()
state.shared_done.record(state.stream)
self.experts.group(ids)
return self.finish(b, out, latent, ids, weights, shared, consume)
state.begin.record(main)
side = state.urgent if self.front.gated(b) else state.stream
with torch.cuda.stream(side):
side.wait_event(state.begin)
shared, beside = self.front.shared_expert(x, self.tail)
if beside is not None:
state.gate_done.record(side)
beside()
state.shared_done.record(side)
ids, weights, latent = self.front.route(x)
self.experts.group(ids) # pregrouped unit table, beside the shared expert
if before or (beside is not None and self.join.get(b, 1)):
main.wait_event(state.shared_done if before else state.gate_done)
return self.finish(b, out, latent, ids, weights, shared, consume, before)
def finish(self, b, out, latent, ids, weights, shared, consume, before=False):
"""The routed experts, the next layer's prefetch fork, then the tail once the
shared expert's side stream is done. Early-released batches skip that join:
their tail Lamport-polls the shared rows the side stream's last kernel pushes,
which also orders the side stream's reads of x before the next layer rewrites
it, so the tail keeps its single (programmatic) dependency on the experts and
launches as their CTAs exit; the step joins the urgent stream at its end
(``MoEScratch.join``)."""
main = torch.cuda.current_stream(self.state.device)
ahead = self.ahead.get(b)
expanded = self.experts.run(latent, ids, weights, ahead)
if ahead is None and self.prefetch is not None:
self.prefetch.fork("next_attention", self.layer_idx, b)
if self.front.gated(b):
self.state.pending = True
elif not before:
main.wait_event(self.state.shared_done)
return self.tail.run(expanded, weights, shared, out, consume=consume)