Kernels
github-actions[bot] commited on
Commit
f88998f
·
1 Parent(s): fceb334

Add built binary [ci skip]

Browse files
Files changed (36) hide show
  1. build/torch27-cxx11-cu118-x86_64-linux/optimizer/_ops.py +3 -3
  2. build/torch27-cxx11-cu118-x86_64-linux/optimizer/{_optimizer_9c21645_dirty.abi3.so → _optimizer_fceb334_dirty.abi3.so} +1 -1
  3. build/torch27-cxx11-cu118-x86_64-linux/optimizer/matmul_transpose_triton.py +106 -0
  4. build/torch27-cxx11-cu118-x86_64-linux/optimizer/muon.py +272 -104
  5. build/torch27-cxx11-cu126-x86_64-linux/optimizer/_ops.py +3 -3
  6. build/{torch28-cxx11-cu126-x86_64-linux/optimizer/_optimizer_9c21645_dirty.abi3.so → torch27-cxx11-cu126-x86_64-linux/optimizer/_optimizer_fceb334_dirty.abi3.so} +1 -1
  7. build/torch27-cxx11-cu126-x86_64-linux/optimizer/matmul_transpose_triton.py +106 -0
  8. build/torch27-cxx11-cu126-x86_64-linux/optimizer/muon.py +272 -104
  9. build/torch27-cxx11-cu128-x86_64-linux/optimizer/_ops.py +3 -3
  10. build/{torch28-cxx11-cu128-x86_64-linux/optimizer/_optimizer_9c21645_dirty.abi3.so → torch27-cxx11-cu128-x86_64-linux/optimizer/_optimizer_fceb334_dirty.abi3.so} +1 -1
  11. build/torch27-cxx11-cu128-x86_64-linux/optimizer/matmul_transpose_triton.py +106 -0
  12. build/torch27-cxx11-cu128-x86_64-linux/optimizer/muon.py +272 -104
  13. build/torch27-cxx11-rocm63-x86_64-linux/optimizer/_ops.py +3 -3
  14. build/torch27-cxx11-rocm63-x86_64-linux/optimizer/{_optimizer_9c21645_dirty.abi3.so → _optimizer_fceb334_dirty.abi3.so} +1 -1
  15. build/torch27-cxx11-rocm63-x86_64-linux/optimizer/matmul_transpose_triton.py +106 -0
  16. build/torch27-cxx11-rocm63-x86_64-linux/optimizer/muon.py +272 -104
  17. build/torch28-cxx11-cu126-x86_64-linux/optimizer/_ops.py +3 -3
  18. build/{torch27-cxx11-cu126-x86_64-linux/optimizer/_optimizer_9c21645_dirty.abi3.so → torch28-cxx11-cu126-x86_64-linux/optimizer/_optimizer_fceb334_dirty.abi3.so} +1 -1
  19. build/torch28-cxx11-cu126-x86_64-linux/optimizer/matmul_transpose_triton.py +106 -0
  20. build/torch28-cxx11-cu126-x86_64-linux/optimizer/muon.py +272 -104
  21. build/torch28-cxx11-cu128-x86_64-linux/optimizer/_ops.py +3 -3
  22. build/{torch28-cxx11-cu129-x86_64-linux/optimizer/_optimizer_9c21645_dirty.abi3.so → torch28-cxx11-cu128-x86_64-linux/optimizer/_optimizer_fceb334_dirty.abi3.so} +1 -1
  23. build/torch28-cxx11-cu128-x86_64-linux/optimizer/matmul_transpose_triton.py +106 -0
  24. build/torch28-cxx11-cu128-x86_64-linux/optimizer/muon.py +272 -104
  25. build/torch28-cxx11-cu129-x86_64-linux/optimizer/_ops.py +3 -3
  26. build/{torch27-cxx11-cu128-x86_64-linux/optimizer/_optimizer_9c21645_dirty.abi3.so → torch28-cxx11-cu129-x86_64-linux/optimizer/_optimizer_fceb334_dirty.abi3.so} +1 -1
  27. build/torch28-cxx11-cu129-x86_64-linux/optimizer/matmul_transpose_triton.py +106 -0
  28. build/torch28-cxx11-cu129-x86_64-linux/optimizer/muon.py +272 -104
  29. build/torch28-cxx11-rocm63-x86_64-linux/optimizer/_ops.py +3 -3
  30. build/torch28-cxx11-rocm63-x86_64-linux/optimizer/{_optimizer_9c21645_dirty.abi3.so → _optimizer_fceb334_dirty.abi3.so} +1 -1
  31. build/torch28-cxx11-rocm63-x86_64-linux/optimizer/matmul_transpose_triton.py +106 -0
  32. build/torch28-cxx11-rocm63-x86_64-linux/optimizer/muon.py +272 -104
  33. build/torch28-cxx11-rocm64-x86_64-linux/optimizer/_ops.py +3 -3
  34. build/torch28-cxx11-rocm64-x86_64-linux/optimizer/{_optimizer_9c21645_dirty.abi3.so → _optimizer_fceb334_dirty.abi3.so} +1 -1
  35. build/torch28-cxx11-rocm64-x86_64-linux/optimizer/matmul_transpose_triton.py +106 -0
  36. build/torch28-cxx11-rocm64-x86_64-linux/optimizer/muon.py +272 -104
build/torch27-cxx11-cu118-x86_64-linux/optimizer/_ops.py CHANGED
@@ -1,9 +1,9 @@
1
  import torch
2
- from . import _optimizer_9c21645_dirty
3
- ops = torch.ops._optimizer_9c21645_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
- return f"_optimizer_9c21645_dirty::{op_name}"
 
1
  import torch
2
+ from . import _optimizer_fceb334_dirty
3
+ ops = torch.ops._optimizer_fceb334_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
+ return f"_optimizer_fceb334_dirty::{op_name}"
build/torch27-cxx11-cu118-x86_64-linux/optimizer/{_optimizer_9c21645_dirty.abi3.so → _optimizer_fceb334_dirty.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:bf8b97161714dff91953d26ae0bf59ebc9f3653ce57a3998723cc08aa97b71e6
3
  size 1787368
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:be3c6bfa9c13eae63d8c208954dfb399939bfe64065d0187464db5ff2dce5c30
3
  size 1787368
build/torch27-cxx11-cu118-x86_64-linux/optimizer/matmul_transpose_triton.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import triton
3
+ import triton.language as tl
4
+
5
+
6
+ def get_autotune_config():
7
+ return [
8
+ triton.Config(
9
+ {
10
+ 'BLOCK_SIZE_M': blk_m,
11
+ 'BLOCK_SIZE_K': blk_k,
12
+ 'GROUP_SIZE_M': grp_sz
13
+ },
14
+ num_stages=n_stages,
15
+ num_warps=n_warps) for blk_m in [32, 64, 128]
16
+ for blk_k in [32, 64] for grp_sz in [8] for n_stages in [3, 4, 5]
17
+ for n_warps in [4, 8]
18
+ ]
19
+
20
+
21
+ @triton.autotune(
22
+ configs=get_autotune_config(),
23
+ key=['M', 'K'],
24
+ )
25
+ @triton.jit
26
+ def mmt_kernel(x, y, M, K, stride_xm, stride_xk, stride_ym, stride_yn,
27
+ BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_K: tl.constexpr,
28
+ GROUP_SIZE_M: tl.constexpr):
29
+ """
30
+ Core kernel jit function of matmul_transpose that computes y = x @ x.T
31
+ The code is a simple adaptation from the triton `matmul` tutorial:
32
+ https://triton-lang.org/main/getting-started/tutorials/03-matrix-multiplication.html
33
+ """
34
+ pid = tl.program_id(axis=0)
35
+ num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
36
+ num_pid_n = tl.cdiv(M, BLOCK_SIZE_M)
37
+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
38
+ group_id = pid // num_pid_in_group
39
+ first_pid_m = group_id * GROUP_SIZE_M
40
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
41
+ pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
42
+ pid_n = (pid % num_pid_in_group) // group_size_m
43
+ if pid_m > pid_n:
44
+ return
45
+
46
+ offs_xm = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
47
+ offs_xn = (pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
48
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
49
+ # we use a & b ptrs to denote different rows of x.
50
+ a_ptrs = x + (offs_xm[:, None] * stride_xm + offs_k[None, :] * stride_xk)
51
+ b_ptrs = x + (offs_xn[:, None] * stride_xm + offs_k[None, :] * stride_xk)
52
+
53
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_M), dtype=tl.float32)
54
+
55
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
56
+ a = tl.load(a_ptrs,
57
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
58
+ other=0.0)
59
+ b = tl.load(b_ptrs,
60
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
61
+ other=0.0)
62
+ accumulator = tl.dot(a, tl.permute(b, (1, 0)), accumulator)
63
+ a_ptrs += BLOCK_SIZE_K * stride_xk
64
+ b_ptrs += BLOCK_SIZE_K * stride_xk
65
+ # use dtype.element_ty to accommodate different input datatypes as in cpp templates
66
+ # https://github.com/triton-lang/triton/issues/2252
67
+ c = accumulator.to(x.dtype.element_ty)
68
+
69
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
70
+ offs_cn = pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
71
+ c_ptrs = y + stride_ym * offs_cm[:, None] + stride_yn * offs_cn[None, :]
72
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < M)
73
+ tl.store(c_ptrs, c, mask=c_mask)
74
+
75
+ # transpose and copy
76
+ if pid_m < pid_n:
77
+ ct_ptrs = y + stride_ym * offs_cn[:,
78
+ None] + stride_yn * offs_cm[None, :]
79
+ ct_mask = (offs_cn[:, None] < M) & (offs_cm[None, :] < M)
80
+ tl.store(ct_ptrs, tl.permute(c, (1, 0)), mask=ct_mask)
81
+
82
+
83
+ def matmul_transpose_assign(d_in, d_out):
84
+ assert d_in.is_cuda, "Input `d_in` must be a CUDA tensor"
85
+ assert d_out.is_cuda, "Input `d_out` must be a CUDA tensor"
86
+ assert d_in.device == d_out.device, "Inputs `d_in` and `d_out` must be on the same CUDA device"
87
+ assert d_in.dtype == d_out.dtype, "Inputs must have the same data type"
88
+ assert d_in.ndim == 2, "Input `d_in` must be a 2D tensor"
89
+ assert d_out.ndim == 2, "Input `d_out` must be a 2D tensor"
90
+ assert d_in.size(0) == d_out.size(0) == d_out.size(0), \
91
+ "First dimension of `d_in` must match first and second dimension of `d_out`"
92
+
93
+ d_in = d_in.contiguous()
94
+ M, K = d_in.shape
95
+ grid = lambda META: (triton.cdiv(M, META['BLOCK_SIZE_M']) * triton.cdiv(
96
+ M, META['BLOCK_SIZE_M']), )
97
+ with torch.cuda.device(d_in.device.index):
98
+ mmt_kernel[grid](d_in, d_out, M, K, d_in.stride(0), d_in.stride(1),
99
+ d_out.stride(0), d_out.stride(1))
100
+
101
+
102
+ def matmul_transpose(d_in):
103
+ M, _ = d_in.shape
104
+ d_out = torch.empty((M, M), device=d_in.device, dtype=d_in.dtype)
105
+ matmul_transpose_assign(d_in, d_out)
106
+ return d_out
build/torch27-cxx11-cu118-x86_64-linux/optimizer/muon.py CHANGED
@@ -8,14 +8,19 @@ import torch
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
 
 
11
  logger = logging.getLogger(__name__)
12
 
 
 
13
 
14
  # This code snippet is a modified version adapted from the following GitHub repositories:
15
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
16
  # Muon's Newton–Schulz iteration causes high variance in singular values
17
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
18
  @torch.no_grad()
 
19
  def _zeropower_via_newtonschulz5(G, steps):
20
  """
21
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
@@ -27,13 +32,15 @@ def _zeropower_via_newtonschulz5(G, steps):
27
  performance at all relative to UV^T, where USV^T = G is the SVD.
28
  """
29
  assert len(G.shape) == 2
30
- assert G.dtype == torch.bfloat16
31
  X = G # no manual typecast
32
 
33
  if G.size(0) > G.size(1):
34
  X = X.T
35
  # Ensure spectral norm is at most 1
36
  X = X / (X.norm() + 1e-7)
 
 
37
  # Perform the NS iterations
38
  for a, b, c in [
39
  (4.0848, -6.8946, 2.9270),
@@ -42,13 +49,10 @@ def _zeropower_via_newtonschulz5(G, steps):
42
  (2.8769, -3.1427, 1.2046),
43
  (2.8366, -3.0525, 1.2012),
44
  ]:
45
- A = X @ X.T
46
- # B = (
47
- # b * A + c * A @ A
48
- # )
49
- B = torch.addmm(A, A, A, alpha=c, beta=b)
50
- # X = a * X + B @ X
51
- X = torch.addmm(X, B, X, alpha=1.0, beta=a)
52
 
53
  if G.size(0) > G.size(1):
54
  X = X.T
@@ -69,51 +73,131 @@ class _muon_state:
69
  qk_clip_state = None
70
 
71
 
 
 
 
 
 
 
 
 
72
  @torch.no_grad()
73
- def _gather(p, state, rank, comm_stream, none_grad):
74
- """
75
- Gather the gradients to worker_rank.
76
- If none_grad is True, free p.grad after the gather.
77
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  with torch.cuda.stream(comm_stream):
79
- g = p.grad
 
80
 
81
- if rank == state.worker_rank:
82
- num_ranks = dist.get_world_size(group=state.process_group)
83
- gather_list = [
84
- torch.empty_like(g.to_local(), dtype=torch.bfloat16)
85
- for _ in range(num_ranks)
86
- ]
87
- else:
88
- gather_list = None
89
-
90
- g = g.to(torch.bfloat16)
91
- torch.distributed.gather(
92
- g.to_local(),
93
- dst=state.worker_rank,
94
- gather_list=gather_list,
95
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  )
97
- if rank == state.worker_rank:
98
- if state.gathered_grad is not None:
99
- raise RuntimeError(
100
- "Gather event already exists, which should not happen.")
101
- state.gathered_grad = torch.cat(gather_list, dim=0)
102
- state.gather_event = torch.cuda.Event()
103
- state.gather_event.record()
104
- else:
105
- state.gathered_grad = None
106
- state.gather_event = None
107
- gather_list = None
108
- if none_grad:
109
- # We can safely free p.grad without calling record_stream:
110
- # p.grad.to_local().record_stream(comm_stream)
111
- # Explanation:
112
- # 1. p.grad is created on the default stream, but the default stream
113
- # is synchronized with the comm stream later.
114
- # 2. There is no further activity on the default stream before the optimizer finishes.
115
- # Therefore, it is safe to free p.grad directly on the comm stream.
116
- p.grad = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
 
119
  @torch.no_grad()
@@ -127,45 +211,119 @@ def _compute_u(p, state, steps, rank, compute_stream):
127
  raise RuntimeError("Gather event must be set before compute.")
128
  compute_stream.wait_event(state.gather_event)
129
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
 
130
  state.computed_u = u
131
- state.scattered_u = torch.empty_like(p.to_local(),
132
- dtype=torch.bfloat16)
133
- state.compute_event = torch.cuda.Event()
134
- state.compute_event.record()
135
- u = None
136
 
137
 
138
  @torch.no_grad()
139
- def _scatter(p, state, rank, comm_stream):
140
- """
141
- Scatter the computed_u from worker_rank to all ranks.
142
- """
 
 
 
 
 
 
143
 
 
 
144
  with torch.cuda.stream(comm_stream):
145
- if state.compute_event is None:
146
- raise RuntimeError("Compute event must be set before scatter.")
147
- comm_stream.wait_event(state.compute_event)
 
 
148
 
149
- if rank == state.worker_rank:
150
- num_ranks = dist.get_world_size(group=state.process_group)
151
- # Clear the gathered gradient to free memory
152
- state.gathered_grad = None
153
 
154
- u = state.computed_u
155
- scatter_list = list(torch.split(u, p.size(0) // num_ranks, dim=0))
156
- scatter_list = [s.contiguous() for s in scatter_list]
157
- else:
158
- scatter_list = None
 
 
 
159
 
160
- torch.distributed.scatter(
161
- state.scattered_u,
162
- scatter_list=scatter_list,
163
- src=state.worker_rank,
164
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  )
166
- state.scatter_event = torch.cuda.Event()
167
- state.scatter_event.record()
168
- scatter_list = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
 
171
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
@@ -444,11 +602,14 @@ class Muon(torch.optim.Optimizer):
444
  if mesh is None:
445
  mesh = p.device_mesh
446
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
 
447
  elif mesh != p.device_mesh:
448
  raise ValueError("All parameters must be on the same mesh.")
449
 
 
450
  param_to_state[id(p)] = _muon_state()
451
- param_to_state[id(p)].worker_rank = shard_mesh[round_robin].item()
 
452
  param_to_state[id(p)].process_group = process_group
453
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
454
  param_to_state[id(p)].qk_clip_state = qk_clip_state
@@ -478,7 +639,7 @@ class Muon(torch.optim.Optimizer):
478
  else:
479
  g = buf
480
 
481
- u = _zeropower_via_newtonschulz5(g.bfloat16(),
482
  steps=group["ns_steps"])
483
 
484
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
@@ -493,15 +654,12 @@ class Muon(torch.optim.Optimizer):
493
  def _update_g(self, p, g, group, momentum):
494
  # calc update
495
  state = self.state[p]
496
- if "momentum_buffer" not in state:
497
- state["momentum_buffer"] = torch.zeros_like(g)
498
- buf = state["momentum_buffer"]
499
- buf.mul_(momentum).add_(g)
500
  if group["nesterov"]:
501
- g = g.add(buf, alpha=momentum)
502
- else:
503
- g = buf
504
- return g
505
 
506
  @staticmethod
507
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
@@ -585,11 +743,15 @@ class Muon(torch.optim.Optimizer):
585
  param_to_state, ordered_params = self.init_state_and_assign_params(
586
  names, params, group, qk_logits)
587
 
588
- def enqueue_gathers(start_idx, chunk_size):
589
- for p in ordered_params[start_idx:start_idx + chunk_size]:
590
- state = param_to_state[id(p)]
591
- _gather(p, state, self.rank, self.comm_stream,
592
- group["none_grad"])
 
 
 
 
593
 
594
  def enqueue_computes(start_idx, chunk_size):
595
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -597,10 +759,14 @@ class Muon(torch.optim.Optimizer):
597
  _compute_u(p, state, group["ns_steps"], self.rank,
598
  self.compute_stream)
599
 
600
- def enqueue_scatters(start_idx, chunk_size):
601
- for p in ordered_params[start_idx:start_idx + chunk_size]:
602
- state = param_to_state[id(p)]
603
- _scatter(p, state, self.rank, self.comm_stream)
 
 
 
 
604
 
605
  def enqueue_update_param(start_idx, chunk_size):
606
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -615,14 +781,16 @@ class Muon(torch.optim.Optimizer):
615
  # Wait grad update
616
  self.comm_stream.wait_stream(torch.cuda.current_stream())
617
 
618
- enqueue_gathers(0, chunk_size)
 
 
 
 
619
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
620
- enqueue_computes(i, chunk_size)
621
- if i > 0:
622
- enqueue_update_param(i - chunk_size, chunk_size)
623
- enqueue_gathers(i + chunk_size, chunk_size)
624
- enqueue_scatters(i, chunk_size)
625
- enqueue_update_param(i, chunk_size)
626
 
627
  # Wait the last update_param to finish
628
  torch.cuda.current_stream().wait_stream(self.compute_stream)
 
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
11
+ from .matmul_transpose_triton import matmul_transpose_assign
12
+
13
  logger = logging.getLogger(__name__)
14
 
15
+ COMM_DTYPE = torch.bfloat16
16
+
17
 
18
  # This code snippet is a modified version adapted from the following GitHub repositories:
19
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
20
  # Muon's Newton–Schulz iteration causes high variance in singular values
21
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
22
  @torch.no_grad()
23
+ # matmul_transpose_assign from : https://github.com/nil0x9/flash-muon
24
  def _zeropower_via_newtonschulz5(G, steps):
25
  """
26
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
 
32
  performance at all relative to UV^T, where USV^T = G is the SVD.
33
  """
34
  assert len(G.shape) == 2
35
+ assert G.dtype == COMM_DTYPE
36
  X = G # no manual typecast
37
 
38
  if G.size(0) > G.size(1):
39
  X = X.T
40
  # Ensure spectral norm is at most 1
41
  X = X / (X.norm() + 1e-7)
42
+ buf1 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
43
+ buf2 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
44
  # Perform the NS iterations
45
  for a, b, c in [
46
  (4.0848, -6.8946, 2.9270),
 
49
  (2.8769, -3.1427, 1.2046),
50
  (2.8366, -3.0525, 1.2012),
51
  ]:
52
+ matmul_transpose_assign(X, buf1)
53
+ matmul_transpose_assign(buf1, buf2)
54
+ buf1.mul_(b).add_(buf2, alpha=c)
55
+ X = torch.addmm(X, buf1, X, alpha=1.0, beta=a)
 
 
 
56
 
57
  if G.size(0) > G.size(1):
58
  X = X.T
 
73
  qk_clip_state = None
74
 
75
 
76
+ def split_elems_for_src(param, state, src_rank, num_ranks) -> int:
77
+ rows = param.shape[0]
78
+ cols = int(param.numel() // rows)
79
+ base, rem = divmod(rows, num_ranks)
80
+ my_rows = base + (1 if src_rank < rem else 0)
81
+ return my_rows * cols
82
+
83
+
84
  @torch.no_grad()
85
+ def _alloc_gathered_grad(params, param_to_state, rank, compute_stream):
86
+ with torch.cuda.stream(compute_stream):
87
+ for p in params:
88
+ state = param_to_state[id(p)]
89
+ if rank == state.worker_rank:
90
+ num_ranks = dist.get_world_size(group=state.process_group)
91
+ state.gathered_grad = torch.empty(p.grad.numel(),
92
+ dtype=COMM_DTYPE,
93
+ device="cuda")
94
+ else:
95
+ state.gathered_grad = None
96
+
97
+ alloc_event = torch.cuda.Event()
98
+ alloc_event.record(compute_stream)
99
+ return alloc_event
100
+
101
+
102
+ @torch.no_grad()
103
+ def _all2all_gather(params, param_to_state, rank, comm_stream, none_grad,
104
+ alloc_event):
105
  with torch.cuda.stream(comm_stream):
106
+ process_group = param_to_state[id(params[0])].process_group
107
+ num_ranks = dist.get_world_size(group=process_group)
108
 
109
+ # Calculate sending tensors
110
+ per_dst = [[] for _ in range(num_ranks)]
111
+ send_counts = [0] * num_ranks
112
+ for p in params:
113
+ state = param_to_state[id(p)]
114
+ dst = state.worker_rank
115
+ assert dst < num_ranks
116
+ shard_elems = split_elems_for_src(p, state, rank, num_ranks)
117
+ g = p.grad
118
+ g = g.to_local().to(COMM_DTYPE).contiguous().view(-1)
119
+ assert g.numel() == shard_elems
120
+ per_dst[dst].append(g)
121
+ send_counts[dst] += shard_elems
122
+
123
+ assert all(
124
+ len(v) > 0
125
+ for v in per_dst), "all params should be sharded to all devices"
126
+
127
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
128
+ owner_params = [
129
+ p for p in params if param_to_state[id(p)].worker_rank == rank
130
+ ]
131
+
132
+ # Calculate receiving tensors
133
+ recv_counts = [0] * num_ranks
134
+ for src in range(num_ranks):
135
+ total = 0
136
+ for p in owner_params:
137
+ state = param_to_state[id(p)]
138
+ assert state.worker_rank == rank
139
+ total += split_elems_for_src(p, state, src, num_ranks)
140
+ recv_counts[src] = total
141
+
142
+ recv_total = sum(recv_counts)
143
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
144
+ dist.all_to_all_single(
145
+ recv_buf,
146
+ send_buf,
147
+ output_split_sizes=recv_counts,
148
+ input_split_sizes=send_counts,
149
+ group=process_group,
150
  )
151
+
152
+ # Reconstructs gathered grad from the received buffer
153
+ #
154
+ # recv_buf (num ranks = 3)
155
+ #
156
+ # From rank 0 From rank 1 From rank 2
157
+ # | p1_0, p2_0, p3_0 | p1_1, p2_1, p3_1 | p1_2, p2_2, p3_2 |
158
+ #
159
+ # Outer loop:
160
+ # rank 0 -> rank 1 -> rank2
161
+ #
162
+ # Inner loop:
163
+ # p1_n -> p2_n -> p3_n
164
+
165
+ comm_stream.wait_event(alloc_event)
166
+
167
+ off = 0
168
+ write_offsets = {id(p): 0 for p in owner_params}
169
+ for src in range(num_ranks):
170
+ if recv_counts[src] == 0:
171
+ continue
172
+
173
+ block = recv_counts[src]
174
+ inner_off = 0
175
+ for p in owner_params:
176
+ state = param_to_state[id(p)]
177
+ assert state.worker_rank == rank
178
+ n = split_elems_for_src(p, state, src, num_ranks)
179
+ assert n > 0
180
+
181
+ sg = recv_buf.narrow(0, off + inner_off, n)
182
+ woff = write_offsets[id(p)]
183
+ dst = state.gathered_grad.narrow(0, woff, n)
184
+ dst.copy_(sg)
185
+
186
+ write_offsets[id(p)] += n
187
+ inner_off += n
188
+ off += block
189
+
190
+ for p in params:
191
+ state = param_to_state[id(p)]
192
+ if state.worker_rank == rank:
193
+ state.gathered_grad = state.gathered_grad.view_as(p)
194
+ state.gather_event = torch.cuda.Event()
195
+ state.gather_event.record(comm_stream)
196
+ else:
197
+ state.gathered_grad = None
198
+ state.gather_event = None
199
+ if none_grad:
200
+ p.grad = None
201
 
202
 
203
  @torch.no_grad()
 
211
  raise RuntimeError("Gather event must be set before compute.")
212
  compute_stream.wait_event(state.gather_event)
213
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
214
+ state.gathered_grad = None
215
  state.computed_u = u
216
+ state.compute_event = torch.cuda.Event()
217
+ state.compute_event.record()
218
+ else:
219
+ state.computed_u = None
220
+ state.compute_event = None
221
 
222
 
223
  @torch.no_grad()
224
+ def _alloc_scattered_u(params, param_to_state, rank, compute_stream):
225
+ with torch.cuda.stream(compute_stream):
226
+ for p in params:
227
+ state = param_to_state[id(p)]
228
+ state.scattered_u = torch.empty_like(p.to_local(),
229
+ dtype=COMM_DTYPE)
230
+
231
+ alloc_event = torch.cuda.Event()
232
+ alloc_event.record(compute_stream)
233
+ return alloc_event
234
 
235
+
236
+ def _all2all_scatter(params, param_to_state, rank, comm_stream, alloc_event):
237
  with torch.cuda.stream(comm_stream):
238
+ process_group = param_to_state[id(params[0])].process_group
239
+ num_ranks = dist.get_world_size(group=process_group)
240
+ owner_params = [
241
+ p for p in params if param_to_state[id(p)].worker_rank == rank
242
+ ]
243
 
244
+ per_dst = [[] for _ in range(num_ranks)]
245
+ send_counts = [0] * num_ranks
 
 
246
 
247
+ if owner_params:
248
+ for p in owner_params:
249
+ state = param_to_state[id(p)]
250
+ if state.compute_event is None:
251
+ raise RuntimeError(
252
+ "Compute event must be set before scatter.")
253
+ comm_stream.wait_event(state.compute_event)
254
+ state.gathered_grad = None
255
 
256
+ assert state.computed_u is not None
257
+
258
+ u_full = state.computed_u.to(COMM_DTYPE).contiguous().view(-1)
259
+
260
+ offset = 0
261
+ for dst in range(num_ranks):
262
+ n = split_elems_for_src(p, state, dst, num_ranks)
263
+ assert n > 0
264
+
265
+ su = u_full.narrow(0, offset, n)
266
+ per_dst[dst].append(su)
267
+ send_counts[dst] += n
268
+ offset += n
269
+
270
+ assert offset == u_full.numel()
271
+
272
+ if any(len(v) > 0 for v in per_dst):
273
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
274
+ else:
275
+ # all_to_all requires participation from all ranks
276
+ # Even non-owner ranks must join the collective call
277
+ send_buf = torch.empty(0, dtype=COMM_DTYPE, device="cuda")
278
+
279
+ recv_counts = [0] * num_ranks
280
+ for src in range(num_ranks):
281
+ total = 0
282
+ for p in params:
283
+ state = param_to_state[id(p)]
284
+ if state.worker_rank != src:
285
+ continue
286
+ total += split_elems_for_src(p, state, rank, num_ranks)
287
+ recv_counts[src] = total
288
+
289
+ recv_total = sum(recv_counts)
290
+ assert recv_total > 0
291
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
292
+
293
+ dist.all_to_all_single(
294
+ recv_buf,
295
+ send_buf,
296
+ output_split_sizes=recv_counts,
297
+ input_split_sizes=send_counts,
298
+ group=process_group,
299
  )
300
+
301
+ comm_stream.wait_event(alloc_event)
302
+
303
+ off = 0
304
+ for src in range(num_ranks):
305
+ block = recv_counts[src]
306
+ if block == 0:
307
+ continue
308
+
309
+ inner_off = 0
310
+ for p in params:
311
+ state = param_to_state[id(p)]
312
+ if state.worker_rank != src:
313
+ continue
314
+ n = split_elems_for_src(p, state, rank, num_ranks)
315
+ assert n > 0
316
+
317
+ flat_local = recv_buf.narrow(0, off + inner_off,
318
+ n).view_as(p.to_local())
319
+ state.scattered_u.copy_(flat_local)
320
+
321
+ state.scatter_event = torch.cuda.Event()
322
+ state.scatter_event.record(comm_stream)
323
+ inner_off += n
324
+
325
+ assert inner_off == block
326
+ off += block
327
 
328
 
329
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
 
602
  if mesh is None:
603
  mesh = p.device_mesh
604
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
605
+ self.rank = dist.get_rank(group=process_group)
606
  elif mesh != p.device_mesh:
607
  raise ValueError("All parameters must be on the same mesh.")
608
 
609
+ num_ranks = dist.get_world_size(group=process_group)
610
  param_to_state[id(p)] = _muon_state()
611
+ param_to_state[id(
612
+ p)].worker_rank = shard_mesh[round_robin].item() % num_ranks
613
  param_to_state[id(p)].process_group = process_group
614
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
615
  param_to_state[id(p)].qk_clip_state = qk_clip_state
 
639
  else:
640
  g = buf
641
 
642
+ u = _zeropower_via_newtonschulz5(g.to(COMM_DTYPE),
643
  steps=group["ns_steps"])
644
 
645
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
 
654
  def _update_g(self, p, g, group, momentum):
655
  # calc update
656
  state = self.state[p]
657
+ buf = state.setdefault("momentum_buffer", torch.zeros_like(g))
658
+ torch.add(g, buf, alpha=momentum, out=buf)
 
 
659
  if group["nesterov"]:
660
+ g.add_(buf, alpha=momentum)
661
+ return g
662
+ return buf
 
663
 
664
  @staticmethod
665
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
 
743
  param_to_state, ordered_params = self.init_state_and_assign_params(
744
  names, params, group, qk_logits)
745
 
746
+ def enqueue_all2all_gather(start_idx, chunk_size):
747
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
748
+ if target_params:
749
+ alloc_event = _alloc_gathered_grad(target_params,
750
+ param_to_state, self.rank,
751
+ self.compute_stream)
752
+ _all2all_gather(target_params, param_to_state, self.rank,
753
+ self.comm_stream, group["none_grad"],
754
+ alloc_event)
755
 
756
  def enqueue_computes(start_idx, chunk_size):
757
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
759
  _compute_u(p, state, group["ns_steps"], self.rank,
760
  self.compute_stream)
761
 
762
+ def enqueue_all2all_scatter(start_idx, chunk_size):
763
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
764
+ if target_params:
765
+ alloc_event = _alloc_scattered_u(target_params, param_to_state,
766
+ self.rank,
767
+ self.compute_stream)
768
+ _all2all_scatter(target_params, param_to_state, self.rank,
769
+ self.comm_stream, alloc_event)
770
 
771
  def enqueue_update_param(start_idx, chunk_size):
772
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
781
  # Wait grad update
782
  self.comm_stream.wait_stream(torch.cuda.current_stream())
783
 
784
+ PRE_STEP = 5
785
+ for i in range(0, PRE_STEP):
786
+ enqueue_all2all_gather(i * chunk_size, chunk_size)
787
+ enqueue_computes(i * chunk_size, chunk_size)
788
+
789
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
790
+ enqueue_all2all_scatter(i, chunk_size)
791
+ enqueue_all2all_gather(i + PRE_STEP * chunk_size, chunk_size)
792
+ enqueue_update_param(i, chunk_size)
793
+ enqueue_computes(i + PRE_STEP * chunk_size, chunk_size)
 
 
794
 
795
  # Wait the last update_param to finish
796
  torch.cuda.current_stream().wait_stream(self.compute_stream)
build/torch27-cxx11-cu126-x86_64-linux/optimizer/_ops.py CHANGED
@@ -1,9 +1,9 @@
1
  import torch
2
- from . import _optimizer_9c21645_dirty
3
- ops = torch.ops._optimizer_9c21645_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
- return f"_optimizer_9c21645_dirty::{op_name}"
 
1
  import torch
2
+ from . import _optimizer_fceb334_dirty
3
+ ops = torch.ops._optimizer_fceb334_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
+ return f"_optimizer_fceb334_dirty::{op_name}"
build/{torch28-cxx11-cu126-x86_64-linux/optimizer/_optimizer_9c21645_dirty.abi3.so → torch27-cxx11-cu126-x86_64-linux/optimizer/_optimizer_fceb334_dirty.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:42ae6ac1cf967d7d23cac7930c8db635105f60631220a60b9cee060d082f40ae
3
  size 1824256
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c31efe0c8bad71c6fbc002f269eff7a30754e1583bb8c892b8ce5cdbdfc5121c
3
  size 1824256
build/torch27-cxx11-cu126-x86_64-linux/optimizer/matmul_transpose_triton.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import triton
3
+ import triton.language as tl
4
+
5
+
6
+ def get_autotune_config():
7
+ return [
8
+ triton.Config(
9
+ {
10
+ 'BLOCK_SIZE_M': blk_m,
11
+ 'BLOCK_SIZE_K': blk_k,
12
+ 'GROUP_SIZE_M': grp_sz
13
+ },
14
+ num_stages=n_stages,
15
+ num_warps=n_warps) for blk_m in [32, 64, 128]
16
+ for blk_k in [32, 64] for grp_sz in [8] for n_stages in [3, 4, 5]
17
+ for n_warps in [4, 8]
18
+ ]
19
+
20
+
21
+ @triton.autotune(
22
+ configs=get_autotune_config(),
23
+ key=['M', 'K'],
24
+ )
25
+ @triton.jit
26
+ def mmt_kernel(x, y, M, K, stride_xm, stride_xk, stride_ym, stride_yn,
27
+ BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_K: tl.constexpr,
28
+ GROUP_SIZE_M: tl.constexpr):
29
+ """
30
+ Core kernel jit function of matmul_transpose that computes y = x @ x.T
31
+ The code is a simple adaptation from the triton `matmul` tutorial:
32
+ https://triton-lang.org/main/getting-started/tutorials/03-matrix-multiplication.html
33
+ """
34
+ pid = tl.program_id(axis=0)
35
+ num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
36
+ num_pid_n = tl.cdiv(M, BLOCK_SIZE_M)
37
+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
38
+ group_id = pid // num_pid_in_group
39
+ first_pid_m = group_id * GROUP_SIZE_M
40
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
41
+ pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
42
+ pid_n = (pid % num_pid_in_group) // group_size_m
43
+ if pid_m > pid_n:
44
+ return
45
+
46
+ offs_xm = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
47
+ offs_xn = (pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
48
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
49
+ # we use a & b ptrs to denote different rows of x.
50
+ a_ptrs = x + (offs_xm[:, None] * stride_xm + offs_k[None, :] * stride_xk)
51
+ b_ptrs = x + (offs_xn[:, None] * stride_xm + offs_k[None, :] * stride_xk)
52
+
53
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_M), dtype=tl.float32)
54
+
55
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
56
+ a = tl.load(a_ptrs,
57
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
58
+ other=0.0)
59
+ b = tl.load(b_ptrs,
60
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
61
+ other=0.0)
62
+ accumulator = tl.dot(a, tl.permute(b, (1, 0)), accumulator)
63
+ a_ptrs += BLOCK_SIZE_K * stride_xk
64
+ b_ptrs += BLOCK_SIZE_K * stride_xk
65
+ # use dtype.element_ty to accommodate different input datatypes as in cpp templates
66
+ # https://github.com/triton-lang/triton/issues/2252
67
+ c = accumulator.to(x.dtype.element_ty)
68
+
69
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
70
+ offs_cn = pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
71
+ c_ptrs = y + stride_ym * offs_cm[:, None] + stride_yn * offs_cn[None, :]
72
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < M)
73
+ tl.store(c_ptrs, c, mask=c_mask)
74
+
75
+ # transpose and copy
76
+ if pid_m < pid_n:
77
+ ct_ptrs = y + stride_ym * offs_cn[:,
78
+ None] + stride_yn * offs_cm[None, :]
79
+ ct_mask = (offs_cn[:, None] < M) & (offs_cm[None, :] < M)
80
+ tl.store(ct_ptrs, tl.permute(c, (1, 0)), mask=ct_mask)
81
+
82
+
83
+ def matmul_transpose_assign(d_in, d_out):
84
+ assert d_in.is_cuda, "Input `d_in` must be a CUDA tensor"
85
+ assert d_out.is_cuda, "Input `d_out` must be a CUDA tensor"
86
+ assert d_in.device == d_out.device, "Inputs `d_in` and `d_out` must be on the same CUDA device"
87
+ assert d_in.dtype == d_out.dtype, "Inputs must have the same data type"
88
+ assert d_in.ndim == 2, "Input `d_in` must be a 2D tensor"
89
+ assert d_out.ndim == 2, "Input `d_out` must be a 2D tensor"
90
+ assert d_in.size(0) == d_out.size(0) == d_out.size(0), \
91
+ "First dimension of `d_in` must match first and second dimension of `d_out`"
92
+
93
+ d_in = d_in.contiguous()
94
+ M, K = d_in.shape
95
+ grid = lambda META: (triton.cdiv(M, META['BLOCK_SIZE_M']) * triton.cdiv(
96
+ M, META['BLOCK_SIZE_M']), )
97
+ with torch.cuda.device(d_in.device.index):
98
+ mmt_kernel[grid](d_in, d_out, M, K, d_in.stride(0), d_in.stride(1),
99
+ d_out.stride(0), d_out.stride(1))
100
+
101
+
102
+ def matmul_transpose(d_in):
103
+ M, _ = d_in.shape
104
+ d_out = torch.empty((M, M), device=d_in.device, dtype=d_in.dtype)
105
+ matmul_transpose_assign(d_in, d_out)
106
+ return d_out
build/torch27-cxx11-cu126-x86_64-linux/optimizer/muon.py CHANGED
@@ -8,14 +8,19 @@ import torch
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
 
 
11
  logger = logging.getLogger(__name__)
12
 
 
 
13
 
14
  # This code snippet is a modified version adapted from the following GitHub repositories:
15
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
16
  # Muon's Newton–Schulz iteration causes high variance in singular values
17
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
18
  @torch.no_grad()
 
19
  def _zeropower_via_newtonschulz5(G, steps):
20
  """
21
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
@@ -27,13 +32,15 @@ def _zeropower_via_newtonschulz5(G, steps):
27
  performance at all relative to UV^T, where USV^T = G is the SVD.
28
  """
29
  assert len(G.shape) == 2
30
- assert G.dtype == torch.bfloat16
31
  X = G # no manual typecast
32
 
33
  if G.size(0) > G.size(1):
34
  X = X.T
35
  # Ensure spectral norm is at most 1
36
  X = X / (X.norm() + 1e-7)
 
 
37
  # Perform the NS iterations
38
  for a, b, c in [
39
  (4.0848, -6.8946, 2.9270),
@@ -42,13 +49,10 @@ def _zeropower_via_newtonschulz5(G, steps):
42
  (2.8769, -3.1427, 1.2046),
43
  (2.8366, -3.0525, 1.2012),
44
  ]:
45
- A = X @ X.T
46
- # B = (
47
- # b * A + c * A @ A
48
- # )
49
- B = torch.addmm(A, A, A, alpha=c, beta=b)
50
- # X = a * X + B @ X
51
- X = torch.addmm(X, B, X, alpha=1.0, beta=a)
52
 
53
  if G.size(0) > G.size(1):
54
  X = X.T
@@ -69,51 +73,131 @@ class _muon_state:
69
  qk_clip_state = None
70
 
71
 
 
 
 
 
 
 
 
 
72
  @torch.no_grad()
73
- def _gather(p, state, rank, comm_stream, none_grad):
74
- """
75
- Gather the gradients to worker_rank.
76
- If none_grad is True, free p.grad after the gather.
77
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  with torch.cuda.stream(comm_stream):
79
- g = p.grad
 
80
 
81
- if rank == state.worker_rank:
82
- num_ranks = dist.get_world_size(group=state.process_group)
83
- gather_list = [
84
- torch.empty_like(g.to_local(), dtype=torch.bfloat16)
85
- for _ in range(num_ranks)
86
- ]
87
- else:
88
- gather_list = None
89
-
90
- g = g.to(torch.bfloat16)
91
- torch.distributed.gather(
92
- g.to_local(),
93
- dst=state.worker_rank,
94
- gather_list=gather_list,
95
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  )
97
- if rank == state.worker_rank:
98
- if state.gathered_grad is not None:
99
- raise RuntimeError(
100
- "Gather event already exists, which should not happen.")
101
- state.gathered_grad = torch.cat(gather_list, dim=0)
102
- state.gather_event = torch.cuda.Event()
103
- state.gather_event.record()
104
- else:
105
- state.gathered_grad = None
106
- state.gather_event = None
107
- gather_list = None
108
- if none_grad:
109
- # We can safely free p.grad without calling record_stream:
110
- # p.grad.to_local().record_stream(comm_stream)
111
- # Explanation:
112
- # 1. p.grad is created on the default stream, but the default stream
113
- # is synchronized with the comm stream later.
114
- # 2. There is no further activity on the default stream before the optimizer finishes.
115
- # Therefore, it is safe to free p.grad directly on the comm stream.
116
- p.grad = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
 
119
  @torch.no_grad()
@@ -127,45 +211,119 @@ def _compute_u(p, state, steps, rank, compute_stream):
127
  raise RuntimeError("Gather event must be set before compute.")
128
  compute_stream.wait_event(state.gather_event)
129
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
 
130
  state.computed_u = u
131
- state.scattered_u = torch.empty_like(p.to_local(),
132
- dtype=torch.bfloat16)
133
- state.compute_event = torch.cuda.Event()
134
- state.compute_event.record()
135
- u = None
136
 
137
 
138
  @torch.no_grad()
139
- def _scatter(p, state, rank, comm_stream):
140
- """
141
- Scatter the computed_u from worker_rank to all ranks.
142
- """
 
 
 
 
 
 
143
 
 
 
144
  with torch.cuda.stream(comm_stream):
145
- if state.compute_event is None:
146
- raise RuntimeError("Compute event must be set before scatter.")
147
- comm_stream.wait_event(state.compute_event)
 
 
148
 
149
- if rank == state.worker_rank:
150
- num_ranks = dist.get_world_size(group=state.process_group)
151
- # Clear the gathered gradient to free memory
152
- state.gathered_grad = None
153
 
154
- u = state.computed_u
155
- scatter_list = list(torch.split(u, p.size(0) // num_ranks, dim=0))
156
- scatter_list = [s.contiguous() for s in scatter_list]
157
- else:
158
- scatter_list = None
 
 
 
159
 
160
- torch.distributed.scatter(
161
- state.scattered_u,
162
- scatter_list=scatter_list,
163
- src=state.worker_rank,
164
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  )
166
- state.scatter_event = torch.cuda.Event()
167
- state.scatter_event.record()
168
- scatter_list = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
 
171
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
@@ -444,11 +602,14 @@ class Muon(torch.optim.Optimizer):
444
  if mesh is None:
445
  mesh = p.device_mesh
446
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
 
447
  elif mesh != p.device_mesh:
448
  raise ValueError("All parameters must be on the same mesh.")
449
 
 
450
  param_to_state[id(p)] = _muon_state()
451
- param_to_state[id(p)].worker_rank = shard_mesh[round_robin].item()
 
452
  param_to_state[id(p)].process_group = process_group
453
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
454
  param_to_state[id(p)].qk_clip_state = qk_clip_state
@@ -478,7 +639,7 @@ class Muon(torch.optim.Optimizer):
478
  else:
479
  g = buf
480
 
481
- u = _zeropower_via_newtonschulz5(g.bfloat16(),
482
  steps=group["ns_steps"])
483
 
484
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
@@ -493,15 +654,12 @@ class Muon(torch.optim.Optimizer):
493
  def _update_g(self, p, g, group, momentum):
494
  # calc update
495
  state = self.state[p]
496
- if "momentum_buffer" not in state:
497
- state["momentum_buffer"] = torch.zeros_like(g)
498
- buf = state["momentum_buffer"]
499
- buf.mul_(momentum).add_(g)
500
  if group["nesterov"]:
501
- g = g.add(buf, alpha=momentum)
502
- else:
503
- g = buf
504
- return g
505
 
506
  @staticmethod
507
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
@@ -585,11 +743,15 @@ class Muon(torch.optim.Optimizer):
585
  param_to_state, ordered_params = self.init_state_and_assign_params(
586
  names, params, group, qk_logits)
587
 
588
- def enqueue_gathers(start_idx, chunk_size):
589
- for p in ordered_params[start_idx:start_idx + chunk_size]:
590
- state = param_to_state[id(p)]
591
- _gather(p, state, self.rank, self.comm_stream,
592
- group["none_grad"])
 
 
 
 
593
 
594
  def enqueue_computes(start_idx, chunk_size):
595
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -597,10 +759,14 @@ class Muon(torch.optim.Optimizer):
597
  _compute_u(p, state, group["ns_steps"], self.rank,
598
  self.compute_stream)
599
 
600
- def enqueue_scatters(start_idx, chunk_size):
601
- for p in ordered_params[start_idx:start_idx + chunk_size]:
602
- state = param_to_state[id(p)]
603
- _scatter(p, state, self.rank, self.comm_stream)
 
 
 
 
604
 
605
  def enqueue_update_param(start_idx, chunk_size):
606
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -615,14 +781,16 @@ class Muon(torch.optim.Optimizer):
615
  # Wait grad update
616
  self.comm_stream.wait_stream(torch.cuda.current_stream())
617
 
618
- enqueue_gathers(0, chunk_size)
 
 
 
 
619
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
620
- enqueue_computes(i, chunk_size)
621
- if i > 0:
622
- enqueue_update_param(i - chunk_size, chunk_size)
623
- enqueue_gathers(i + chunk_size, chunk_size)
624
- enqueue_scatters(i, chunk_size)
625
- enqueue_update_param(i, chunk_size)
626
 
627
  # Wait the last update_param to finish
628
  torch.cuda.current_stream().wait_stream(self.compute_stream)
 
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
11
+ from .matmul_transpose_triton import matmul_transpose_assign
12
+
13
  logger = logging.getLogger(__name__)
14
 
15
+ COMM_DTYPE = torch.bfloat16
16
+
17
 
18
  # This code snippet is a modified version adapted from the following GitHub repositories:
19
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
20
  # Muon's Newton–Schulz iteration causes high variance in singular values
21
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
22
  @torch.no_grad()
23
+ # matmul_transpose_assign from : https://github.com/nil0x9/flash-muon
24
  def _zeropower_via_newtonschulz5(G, steps):
25
  """
26
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
 
32
  performance at all relative to UV^T, where USV^T = G is the SVD.
33
  """
34
  assert len(G.shape) == 2
35
+ assert G.dtype == COMM_DTYPE
36
  X = G # no manual typecast
37
 
38
  if G.size(0) > G.size(1):
39
  X = X.T
40
  # Ensure spectral norm is at most 1
41
  X = X / (X.norm() + 1e-7)
42
+ buf1 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
43
+ buf2 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
44
  # Perform the NS iterations
45
  for a, b, c in [
46
  (4.0848, -6.8946, 2.9270),
 
49
  (2.8769, -3.1427, 1.2046),
50
  (2.8366, -3.0525, 1.2012),
51
  ]:
52
+ matmul_transpose_assign(X, buf1)
53
+ matmul_transpose_assign(buf1, buf2)
54
+ buf1.mul_(b).add_(buf2, alpha=c)
55
+ X = torch.addmm(X, buf1, X, alpha=1.0, beta=a)
 
 
 
56
 
57
  if G.size(0) > G.size(1):
58
  X = X.T
 
73
  qk_clip_state = None
74
 
75
 
76
+ def split_elems_for_src(param, state, src_rank, num_ranks) -> int:
77
+ rows = param.shape[0]
78
+ cols = int(param.numel() // rows)
79
+ base, rem = divmod(rows, num_ranks)
80
+ my_rows = base + (1 if src_rank < rem else 0)
81
+ return my_rows * cols
82
+
83
+
84
  @torch.no_grad()
85
+ def _alloc_gathered_grad(params, param_to_state, rank, compute_stream):
86
+ with torch.cuda.stream(compute_stream):
87
+ for p in params:
88
+ state = param_to_state[id(p)]
89
+ if rank == state.worker_rank:
90
+ num_ranks = dist.get_world_size(group=state.process_group)
91
+ state.gathered_grad = torch.empty(p.grad.numel(),
92
+ dtype=COMM_DTYPE,
93
+ device="cuda")
94
+ else:
95
+ state.gathered_grad = None
96
+
97
+ alloc_event = torch.cuda.Event()
98
+ alloc_event.record(compute_stream)
99
+ return alloc_event
100
+
101
+
102
+ @torch.no_grad()
103
+ def _all2all_gather(params, param_to_state, rank, comm_stream, none_grad,
104
+ alloc_event):
105
  with torch.cuda.stream(comm_stream):
106
+ process_group = param_to_state[id(params[0])].process_group
107
+ num_ranks = dist.get_world_size(group=process_group)
108
 
109
+ # Calculate sending tensors
110
+ per_dst = [[] for _ in range(num_ranks)]
111
+ send_counts = [0] * num_ranks
112
+ for p in params:
113
+ state = param_to_state[id(p)]
114
+ dst = state.worker_rank
115
+ assert dst < num_ranks
116
+ shard_elems = split_elems_for_src(p, state, rank, num_ranks)
117
+ g = p.grad
118
+ g = g.to_local().to(COMM_DTYPE).contiguous().view(-1)
119
+ assert g.numel() == shard_elems
120
+ per_dst[dst].append(g)
121
+ send_counts[dst] += shard_elems
122
+
123
+ assert all(
124
+ len(v) > 0
125
+ for v in per_dst), "all params should be sharded to all devices"
126
+
127
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
128
+ owner_params = [
129
+ p for p in params if param_to_state[id(p)].worker_rank == rank
130
+ ]
131
+
132
+ # Calculate receiving tensors
133
+ recv_counts = [0] * num_ranks
134
+ for src in range(num_ranks):
135
+ total = 0
136
+ for p in owner_params:
137
+ state = param_to_state[id(p)]
138
+ assert state.worker_rank == rank
139
+ total += split_elems_for_src(p, state, src, num_ranks)
140
+ recv_counts[src] = total
141
+
142
+ recv_total = sum(recv_counts)
143
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
144
+ dist.all_to_all_single(
145
+ recv_buf,
146
+ send_buf,
147
+ output_split_sizes=recv_counts,
148
+ input_split_sizes=send_counts,
149
+ group=process_group,
150
  )
151
+
152
+ # Reconstructs gathered grad from the received buffer
153
+ #
154
+ # recv_buf (num ranks = 3)
155
+ #
156
+ # From rank 0 From rank 1 From rank 2
157
+ # | p1_0, p2_0, p3_0 | p1_1, p2_1, p3_1 | p1_2, p2_2, p3_2 |
158
+ #
159
+ # Outer loop:
160
+ # rank 0 -> rank 1 -> rank2
161
+ #
162
+ # Inner loop:
163
+ # p1_n -> p2_n -> p3_n
164
+
165
+ comm_stream.wait_event(alloc_event)
166
+
167
+ off = 0
168
+ write_offsets = {id(p): 0 for p in owner_params}
169
+ for src in range(num_ranks):
170
+ if recv_counts[src] == 0:
171
+ continue
172
+
173
+ block = recv_counts[src]
174
+ inner_off = 0
175
+ for p in owner_params:
176
+ state = param_to_state[id(p)]
177
+ assert state.worker_rank == rank
178
+ n = split_elems_for_src(p, state, src, num_ranks)
179
+ assert n > 0
180
+
181
+ sg = recv_buf.narrow(0, off + inner_off, n)
182
+ woff = write_offsets[id(p)]
183
+ dst = state.gathered_grad.narrow(0, woff, n)
184
+ dst.copy_(sg)
185
+
186
+ write_offsets[id(p)] += n
187
+ inner_off += n
188
+ off += block
189
+
190
+ for p in params:
191
+ state = param_to_state[id(p)]
192
+ if state.worker_rank == rank:
193
+ state.gathered_grad = state.gathered_grad.view_as(p)
194
+ state.gather_event = torch.cuda.Event()
195
+ state.gather_event.record(comm_stream)
196
+ else:
197
+ state.gathered_grad = None
198
+ state.gather_event = None
199
+ if none_grad:
200
+ p.grad = None
201
 
202
 
203
  @torch.no_grad()
 
211
  raise RuntimeError("Gather event must be set before compute.")
212
  compute_stream.wait_event(state.gather_event)
213
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
214
+ state.gathered_grad = None
215
  state.computed_u = u
216
+ state.compute_event = torch.cuda.Event()
217
+ state.compute_event.record()
218
+ else:
219
+ state.computed_u = None
220
+ state.compute_event = None
221
 
222
 
223
  @torch.no_grad()
224
+ def _alloc_scattered_u(params, param_to_state, rank, compute_stream):
225
+ with torch.cuda.stream(compute_stream):
226
+ for p in params:
227
+ state = param_to_state[id(p)]
228
+ state.scattered_u = torch.empty_like(p.to_local(),
229
+ dtype=COMM_DTYPE)
230
+
231
+ alloc_event = torch.cuda.Event()
232
+ alloc_event.record(compute_stream)
233
+ return alloc_event
234
 
235
+
236
+ def _all2all_scatter(params, param_to_state, rank, comm_stream, alloc_event):
237
  with torch.cuda.stream(comm_stream):
238
+ process_group = param_to_state[id(params[0])].process_group
239
+ num_ranks = dist.get_world_size(group=process_group)
240
+ owner_params = [
241
+ p for p in params if param_to_state[id(p)].worker_rank == rank
242
+ ]
243
 
244
+ per_dst = [[] for _ in range(num_ranks)]
245
+ send_counts = [0] * num_ranks
 
 
246
 
247
+ if owner_params:
248
+ for p in owner_params:
249
+ state = param_to_state[id(p)]
250
+ if state.compute_event is None:
251
+ raise RuntimeError(
252
+ "Compute event must be set before scatter.")
253
+ comm_stream.wait_event(state.compute_event)
254
+ state.gathered_grad = None
255
 
256
+ assert state.computed_u is not None
257
+
258
+ u_full = state.computed_u.to(COMM_DTYPE).contiguous().view(-1)
259
+
260
+ offset = 0
261
+ for dst in range(num_ranks):
262
+ n = split_elems_for_src(p, state, dst, num_ranks)
263
+ assert n > 0
264
+
265
+ su = u_full.narrow(0, offset, n)
266
+ per_dst[dst].append(su)
267
+ send_counts[dst] += n
268
+ offset += n
269
+
270
+ assert offset == u_full.numel()
271
+
272
+ if any(len(v) > 0 for v in per_dst):
273
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
274
+ else:
275
+ # all_to_all requires participation from all ranks
276
+ # Even non-owner ranks must join the collective call
277
+ send_buf = torch.empty(0, dtype=COMM_DTYPE, device="cuda")
278
+
279
+ recv_counts = [0] * num_ranks
280
+ for src in range(num_ranks):
281
+ total = 0
282
+ for p in params:
283
+ state = param_to_state[id(p)]
284
+ if state.worker_rank != src:
285
+ continue
286
+ total += split_elems_for_src(p, state, rank, num_ranks)
287
+ recv_counts[src] = total
288
+
289
+ recv_total = sum(recv_counts)
290
+ assert recv_total > 0
291
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
292
+
293
+ dist.all_to_all_single(
294
+ recv_buf,
295
+ send_buf,
296
+ output_split_sizes=recv_counts,
297
+ input_split_sizes=send_counts,
298
+ group=process_group,
299
  )
300
+
301
+ comm_stream.wait_event(alloc_event)
302
+
303
+ off = 0
304
+ for src in range(num_ranks):
305
+ block = recv_counts[src]
306
+ if block == 0:
307
+ continue
308
+
309
+ inner_off = 0
310
+ for p in params:
311
+ state = param_to_state[id(p)]
312
+ if state.worker_rank != src:
313
+ continue
314
+ n = split_elems_for_src(p, state, rank, num_ranks)
315
+ assert n > 0
316
+
317
+ flat_local = recv_buf.narrow(0, off + inner_off,
318
+ n).view_as(p.to_local())
319
+ state.scattered_u.copy_(flat_local)
320
+
321
+ state.scatter_event = torch.cuda.Event()
322
+ state.scatter_event.record(comm_stream)
323
+ inner_off += n
324
+
325
+ assert inner_off == block
326
+ off += block
327
 
328
 
329
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
 
602
  if mesh is None:
603
  mesh = p.device_mesh
604
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
605
+ self.rank = dist.get_rank(group=process_group)
606
  elif mesh != p.device_mesh:
607
  raise ValueError("All parameters must be on the same mesh.")
608
 
609
+ num_ranks = dist.get_world_size(group=process_group)
610
  param_to_state[id(p)] = _muon_state()
611
+ param_to_state[id(
612
+ p)].worker_rank = shard_mesh[round_robin].item() % num_ranks
613
  param_to_state[id(p)].process_group = process_group
614
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
615
  param_to_state[id(p)].qk_clip_state = qk_clip_state
 
639
  else:
640
  g = buf
641
 
642
+ u = _zeropower_via_newtonschulz5(g.to(COMM_DTYPE),
643
  steps=group["ns_steps"])
644
 
645
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
 
654
  def _update_g(self, p, g, group, momentum):
655
  # calc update
656
  state = self.state[p]
657
+ buf = state.setdefault("momentum_buffer", torch.zeros_like(g))
658
+ torch.add(g, buf, alpha=momentum, out=buf)
 
 
659
  if group["nesterov"]:
660
+ g.add_(buf, alpha=momentum)
661
+ return g
662
+ return buf
 
663
 
664
  @staticmethod
665
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
 
743
  param_to_state, ordered_params = self.init_state_and_assign_params(
744
  names, params, group, qk_logits)
745
 
746
+ def enqueue_all2all_gather(start_idx, chunk_size):
747
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
748
+ if target_params:
749
+ alloc_event = _alloc_gathered_grad(target_params,
750
+ param_to_state, self.rank,
751
+ self.compute_stream)
752
+ _all2all_gather(target_params, param_to_state, self.rank,
753
+ self.comm_stream, group["none_grad"],
754
+ alloc_event)
755
 
756
  def enqueue_computes(start_idx, chunk_size):
757
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
759
  _compute_u(p, state, group["ns_steps"], self.rank,
760
  self.compute_stream)
761
 
762
+ def enqueue_all2all_scatter(start_idx, chunk_size):
763
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
764
+ if target_params:
765
+ alloc_event = _alloc_scattered_u(target_params, param_to_state,
766
+ self.rank,
767
+ self.compute_stream)
768
+ _all2all_scatter(target_params, param_to_state, self.rank,
769
+ self.comm_stream, alloc_event)
770
 
771
  def enqueue_update_param(start_idx, chunk_size):
772
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
781
  # Wait grad update
782
  self.comm_stream.wait_stream(torch.cuda.current_stream())
783
 
784
+ PRE_STEP = 5
785
+ for i in range(0, PRE_STEP):
786
+ enqueue_all2all_gather(i * chunk_size, chunk_size)
787
+ enqueue_computes(i * chunk_size, chunk_size)
788
+
789
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
790
+ enqueue_all2all_scatter(i, chunk_size)
791
+ enqueue_all2all_gather(i + PRE_STEP * chunk_size, chunk_size)
792
+ enqueue_update_param(i, chunk_size)
793
+ enqueue_computes(i + PRE_STEP * chunk_size, chunk_size)
 
 
794
 
795
  # Wait the last update_param to finish
796
  torch.cuda.current_stream().wait_stream(self.compute_stream)
build/torch27-cxx11-cu128-x86_64-linux/optimizer/_ops.py CHANGED
@@ -1,9 +1,9 @@
1
  import torch
2
- from . import _optimizer_9c21645_dirty
3
- ops = torch.ops._optimizer_9c21645_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
- return f"_optimizer_9c21645_dirty::{op_name}"
 
1
  import torch
2
+ from . import _optimizer_fceb334_dirty
3
+ ops = torch.ops._optimizer_fceb334_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
+ return f"_optimizer_fceb334_dirty::{op_name}"
build/{torch28-cxx11-cu128-x86_64-linux/optimizer/_optimizer_9c21645_dirty.abi3.so → torch27-cxx11-cu128-x86_64-linux/optimizer/_optimizer_fceb334_dirty.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:dae71b7e998e72130093a86f8c983c3379510e23525e3cdcd4afe5c21bf4d3db
3
  size 1883344
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5b7313ff1257a692e4e5cfdec474b10a7a795f2e87edb0099c7e5d21f588634f
3
  size 1883344
build/torch27-cxx11-cu128-x86_64-linux/optimizer/matmul_transpose_triton.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import triton
3
+ import triton.language as tl
4
+
5
+
6
+ def get_autotune_config():
7
+ return [
8
+ triton.Config(
9
+ {
10
+ 'BLOCK_SIZE_M': blk_m,
11
+ 'BLOCK_SIZE_K': blk_k,
12
+ 'GROUP_SIZE_M': grp_sz
13
+ },
14
+ num_stages=n_stages,
15
+ num_warps=n_warps) for blk_m in [32, 64, 128]
16
+ for blk_k in [32, 64] for grp_sz in [8] for n_stages in [3, 4, 5]
17
+ for n_warps in [4, 8]
18
+ ]
19
+
20
+
21
+ @triton.autotune(
22
+ configs=get_autotune_config(),
23
+ key=['M', 'K'],
24
+ )
25
+ @triton.jit
26
+ def mmt_kernel(x, y, M, K, stride_xm, stride_xk, stride_ym, stride_yn,
27
+ BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_K: tl.constexpr,
28
+ GROUP_SIZE_M: tl.constexpr):
29
+ """
30
+ Core kernel jit function of matmul_transpose that computes y = x @ x.T
31
+ The code is a simple adaptation from the triton `matmul` tutorial:
32
+ https://triton-lang.org/main/getting-started/tutorials/03-matrix-multiplication.html
33
+ """
34
+ pid = tl.program_id(axis=0)
35
+ num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
36
+ num_pid_n = tl.cdiv(M, BLOCK_SIZE_M)
37
+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
38
+ group_id = pid // num_pid_in_group
39
+ first_pid_m = group_id * GROUP_SIZE_M
40
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
41
+ pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
42
+ pid_n = (pid % num_pid_in_group) // group_size_m
43
+ if pid_m > pid_n:
44
+ return
45
+
46
+ offs_xm = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
47
+ offs_xn = (pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
48
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
49
+ # we use a & b ptrs to denote different rows of x.
50
+ a_ptrs = x + (offs_xm[:, None] * stride_xm + offs_k[None, :] * stride_xk)
51
+ b_ptrs = x + (offs_xn[:, None] * stride_xm + offs_k[None, :] * stride_xk)
52
+
53
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_M), dtype=tl.float32)
54
+
55
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
56
+ a = tl.load(a_ptrs,
57
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
58
+ other=0.0)
59
+ b = tl.load(b_ptrs,
60
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
61
+ other=0.0)
62
+ accumulator = tl.dot(a, tl.permute(b, (1, 0)), accumulator)
63
+ a_ptrs += BLOCK_SIZE_K * stride_xk
64
+ b_ptrs += BLOCK_SIZE_K * stride_xk
65
+ # use dtype.element_ty to accommodate different input datatypes as in cpp templates
66
+ # https://github.com/triton-lang/triton/issues/2252
67
+ c = accumulator.to(x.dtype.element_ty)
68
+
69
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
70
+ offs_cn = pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
71
+ c_ptrs = y + stride_ym * offs_cm[:, None] + stride_yn * offs_cn[None, :]
72
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < M)
73
+ tl.store(c_ptrs, c, mask=c_mask)
74
+
75
+ # transpose and copy
76
+ if pid_m < pid_n:
77
+ ct_ptrs = y + stride_ym * offs_cn[:,
78
+ None] + stride_yn * offs_cm[None, :]
79
+ ct_mask = (offs_cn[:, None] < M) & (offs_cm[None, :] < M)
80
+ tl.store(ct_ptrs, tl.permute(c, (1, 0)), mask=ct_mask)
81
+
82
+
83
+ def matmul_transpose_assign(d_in, d_out):
84
+ assert d_in.is_cuda, "Input `d_in` must be a CUDA tensor"
85
+ assert d_out.is_cuda, "Input `d_out` must be a CUDA tensor"
86
+ assert d_in.device == d_out.device, "Inputs `d_in` and `d_out` must be on the same CUDA device"
87
+ assert d_in.dtype == d_out.dtype, "Inputs must have the same data type"
88
+ assert d_in.ndim == 2, "Input `d_in` must be a 2D tensor"
89
+ assert d_out.ndim == 2, "Input `d_out` must be a 2D tensor"
90
+ assert d_in.size(0) == d_out.size(0) == d_out.size(0), \
91
+ "First dimension of `d_in` must match first and second dimension of `d_out`"
92
+
93
+ d_in = d_in.contiguous()
94
+ M, K = d_in.shape
95
+ grid = lambda META: (triton.cdiv(M, META['BLOCK_SIZE_M']) * triton.cdiv(
96
+ M, META['BLOCK_SIZE_M']), )
97
+ with torch.cuda.device(d_in.device.index):
98
+ mmt_kernel[grid](d_in, d_out, M, K, d_in.stride(0), d_in.stride(1),
99
+ d_out.stride(0), d_out.stride(1))
100
+
101
+
102
+ def matmul_transpose(d_in):
103
+ M, _ = d_in.shape
104
+ d_out = torch.empty((M, M), device=d_in.device, dtype=d_in.dtype)
105
+ matmul_transpose_assign(d_in, d_out)
106
+ return d_out
build/torch27-cxx11-cu128-x86_64-linux/optimizer/muon.py CHANGED
@@ -8,14 +8,19 @@ import torch
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
 
 
11
  logger = logging.getLogger(__name__)
12
 
 
 
13
 
14
  # This code snippet is a modified version adapted from the following GitHub repositories:
15
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
16
  # Muon's Newton–Schulz iteration causes high variance in singular values
17
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
18
  @torch.no_grad()
 
19
  def _zeropower_via_newtonschulz5(G, steps):
20
  """
21
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
@@ -27,13 +32,15 @@ def _zeropower_via_newtonschulz5(G, steps):
27
  performance at all relative to UV^T, where USV^T = G is the SVD.
28
  """
29
  assert len(G.shape) == 2
30
- assert G.dtype == torch.bfloat16
31
  X = G # no manual typecast
32
 
33
  if G.size(0) > G.size(1):
34
  X = X.T
35
  # Ensure spectral norm is at most 1
36
  X = X / (X.norm() + 1e-7)
 
 
37
  # Perform the NS iterations
38
  for a, b, c in [
39
  (4.0848, -6.8946, 2.9270),
@@ -42,13 +49,10 @@ def _zeropower_via_newtonschulz5(G, steps):
42
  (2.8769, -3.1427, 1.2046),
43
  (2.8366, -3.0525, 1.2012),
44
  ]:
45
- A = X @ X.T
46
- # B = (
47
- # b * A + c * A @ A
48
- # )
49
- B = torch.addmm(A, A, A, alpha=c, beta=b)
50
- # X = a * X + B @ X
51
- X = torch.addmm(X, B, X, alpha=1.0, beta=a)
52
 
53
  if G.size(0) > G.size(1):
54
  X = X.T
@@ -69,51 +73,131 @@ class _muon_state:
69
  qk_clip_state = None
70
 
71
 
 
 
 
 
 
 
 
 
72
  @torch.no_grad()
73
- def _gather(p, state, rank, comm_stream, none_grad):
74
- """
75
- Gather the gradients to worker_rank.
76
- If none_grad is True, free p.grad after the gather.
77
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  with torch.cuda.stream(comm_stream):
79
- g = p.grad
 
80
 
81
- if rank == state.worker_rank:
82
- num_ranks = dist.get_world_size(group=state.process_group)
83
- gather_list = [
84
- torch.empty_like(g.to_local(), dtype=torch.bfloat16)
85
- for _ in range(num_ranks)
86
- ]
87
- else:
88
- gather_list = None
89
-
90
- g = g.to(torch.bfloat16)
91
- torch.distributed.gather(
92
- g.to_local(),
93
- dst=state.worker_rank,
94
- gather_list=gather_list,
95
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  )
97
- if rank == state.worker_rank:
98
- if state.gathered_grad is not None:
99
- raise RuntimeError(
100
- "Gather event already exists, which should not happen.")
101
- state.gathered_grad = torch.cat(gather_list, dim=0)
102
- state.gather_event = torch.cuda.Event()
103
- state.gather_event.record()
104
- else:
105
- state.gathered_grad = None
106
- state.gather_event = None
107
- gather_list = None
108
- if none_grad:
109
- # We can safely free p.grad without calling record_stream:
110
- # p.grad.to_local().record_stream(comm_stream)
111
- # Explanation:
112
- # 1. p.grad is created on the default stream, but the default stream
113
- # is synchronized with the comm stream later.
114
- # 2. There is no further activity on the default stream before the optimizer finishes.
115
- # Therefore, it is safe to free p.grad directly on the comm stream.
116
- p.grad = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
 
119
  @torch.no_grad()
@@ -127,45 +211,119 @@ def _compute_u(p, state, steps, rank, compute_stream):
127
  raise RuntimeError("Gather event must be set before compute.")
128
  compute_stream.wait_event(state.gather_event)
129
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
 
130
  state.computed_u = u
131
- state.scattered_u = torch.empty_like(p.to_local(),
132
- dtype=torch.bfloat16)
133
- state.compute_event = torch.cuda.Event()
134
- state.compute_event.record()
135
- u = None
136
 
137
 
138
  @torch.no_grad()
139
- def _scatter(p, state, rank, comm_stream):
140
- """
141
- Scatter the computed_u from worker_rank to all ranks.
142
- """
 
 
 
 
 
 
143
 
 
 
144
  with torch.cuda.stream(comm_stream):
145
- if state.compute_event is None:
146
- raise RuntimeError("Compute event must be set before scatter.")
147
- comm_stream.wait_event(state.compute_event)
 
 
148
 
149
- if rank == state.worker_rank:
150
- num_ranks = dist.get_world_size(group=state.process_group)
151
- # Clear the gathered gradient to free memory
152
- state.gathered_grad = None
153
 
154
- u = state.computed_u
155
- scatter_list = list(torch.split(u, p.size(0) // num_ranks, dim=0))
156
- scatter_list = [s.contiguous() for s in scatter_list]
157
- else:
158
- scatter_list = None
 
 
 
159
 
160
- torch.distributed.scatter(
161
- state.scattered_u,
162
- scatter_list=scatter_list,
163
- src=state.worker_rank,
164
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  )
166
- state.scatter_event = torch.cuda.Event()
167
- state.scatter_event.record()
168
- scatter_list = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
 
171
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
@@ -444,11 +602,14 @@ class Muon(torch.optim.Optimizer):
444
  if mesh is None:
445
  mesh = p.device_mesh
446
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
 
447
  elif mesh != p.device_mesh:
448
  raise ValueError("All parameters must be on the same mesh.")
449
 
 
450
  param_to_state[id(p)] = _muon_state()
451
- param_to_state[id(p)].worker_rank = shard_mesh[round_robin].item()
 
452
  param_to_state[id(p)].process_group = process_group
453
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
454
  param_to_state[id(p)].qk_clip_state = qk_clip_state
@@ -478,7 +639,7 @@ class Muon(torch.optim.Optimizer):
478
  else:
479
  g = buf
480
 
481
- u = _zeropower_via_newtonschulz5(g.bfloat16(),
482
  steps=group["ns_steps"])
483
 
484
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
@@ -493,15 +654,12 @@ class Muon(torch.optim.Optimizer):
493
  def _update_g(self, p, g, group, momentum):
494
  # calc update
495
  state = self.state[p]
496
- if "momentum_buffer" not in state:
497
- state["momentum_buffer"] = torch.zeros_like(g)
498
- buf = state["momentum_buffer"]
499
- buf.mul_(momentum).add_(g)
500
  if group["nesterov"]:
501
- g = g.add(buf, alpha=momentum)
502
- else:
503
- g = buf
504
- return g
505
 
506
  @staticmethod
507
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
@@ -585,11 +743,15 @@ class Muon(torch.optim.Optimizer):
585
  param_to_state, ordered_params = self.init_state_and_assign_params(
586
  names, params, group, qk_logits)
587
 
588
- def enqueue_gathers(start_idx, chunk_size):
589
- for p in ordered_params[start_idx:start_idx + chunk_size]:
590
- state = param_to_state[id(p)]
591
- _gather(p, state, self.rank, self.comm_stream,
592
- group["none_grad"])
 
 
 
 
593
 
594
  def enqueue_computes(start_idx, chunk_size):
595
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -597,10 +759,14 @@ class Muon(torch.optim.Optimizer):
597
  _compute_u(p, state, group["ns_steps"], self.rank,
598
  self.compute_stream)
599
 
600
- def enqueue_scatters(start_idx, chunk_size):
601
- for p in ordered_params[start_idx:start_idx + chunk_size]:
602
- state = param_to_state[id(p)]
603
- _scatter(p, state, self.rank, self.comm_stream)
 
 
 
 
604
 
605
  def enqueue_update_param(start_idx, chunk_size):
606
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -615,14 +781,16 @@ class Muon(torch.optim.Optimizer):
615
  # Wait grad update
616
  self.comm_stream.wait_stream(torch.cuda.current_stream())
617
 
618
- enqueue_gathers(0, chunk_size)
 
 
 
 
619
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
620
- enqueue_computes(i, chunk_size)
621
- if i > 0:
622
- enqueue_update_param(i - chunk_size, chunk_size)
623
- enqueue_gathers(i + chunk_size, chunk_size)
624
- enqueue_scatters(i, chunk_size)
625
- enqueue_update_param(i, chunk_size)
626
 
627
  # Wait the last update_param to finish
628
  torch.cuda.current_stream().wait_stream(self.compute_stream)
 
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
11
+ from .matmul_transpose_triton import matmul_transpose_assign
12
+
13
  logger = logging.getLogger(__name__)
14
 
15
+ COMM_DTYPE = torch.bfloat16
16
+
17
 
18
  # This code snippet is a modified version adapted from the following GitHub repositories:
19
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
20
  # Muon's Newton–Schulz iteration causes high variance in singular values
21
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
22
  @torch.no_grad()
23
+ # matmul_transpose_assign from : https://github.com/nil0x9/flash-muon
24
  def _zeropower_via_newtonschulz5(G, steps):
25
  """
26
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
 
32
  performance at all relative to UV^T, where USV^T = G is the SVD.
33
  """
34
  assert len(G.shape) == 2
35
+ assert G.dtype == COMM_DTYPE
36
  X = G # no manual typecast
37
 
38
  if G.size(0) > G.size(1):
39
  X = X.T
40
  # Ensure spectral norm is at most 1
41
  X = X / (X.norm() + 1e-7)
42
+ buf1 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
43
+ buf2 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
44
  # Perform the NS iterations
45
  for a, b, c in [
46
  (4.0848, -6.8946, 2.9270),
 
49
  (2.8769, -3.1427, 1.2046),
50
  (2.8366, -3.0525, 1.2012),
51
  ]:
52
+ matmul_transpose_assign(X, buf1)
53
+ matmul_transpose_assign(buf1, buf2)
54
+ buf1.mul_(b).add_(buf2, alpha=c)
55
+ X = torch.addmm(X, buf1, X, alpha=1.0, beta=a)
 
 
 
56
 
57
  if G.size(0) > G.size(1):
58
  X = X.T
 
73
  qk_clip_state = None
74
 
75
 
76
+ def split_elems_for_src(param, state, src_rank, num_ranks) -> int:
77
+ rows = param.shape[0]
78
+ cols = int(param.numel() // rows)
79
+ base, rem = divmod(rows, num_ranks)
80
+ my_rows = base + (1 if src_rank < rem else 0)
81
+ return my_rows * cols
82
+
83
+
84
  @torch.no_grad()
85
+ def _alloc_gathered_grad(params, param_to_state, rank, compute_stream):
86
+ with torch.cuda.stream(compute_stream):
87
+ for p in params:
88
+ state = param_to_state[id(p)]
89
+ if rank == state.worker_rank:
90
+ num_ranks = dist.get_world_size(group=state.process_group)
91
+ state.gathered_grad = torch.empty(p.grad.numel(),
92
+ dtype=COMM_DTYPE,
93
+ device="cuda")
94
+ else:
95
+ state.gathered_grad = None
96
+
97
+ alloc_event = torch.cuda.Event()
98
+ alloc_event.record(compute_stream)
99
+ return alloc_event
100
+
101
+
102
+ @torch.no_grad()
103
+ def _all2all_gather(params, param_to_state, rank, comm_stream, none_grad,
104
+ alloc_event):
105
  with torch.cuda.stream(comm_stream):
106
+ process_group = param_to_state[id(params[0])].process_group
107
+ num_ranks = dist.get_world_size(group=process_group)
108
 
109
+ # Calculate sending tensors
110
+ per_dst = [[] for _ in range(num_ranks)]
111
+ send_counts = [0] * num_ranks
112
+ for p in params:
113
+ state = param_to_state[id(p)]
114
+ dst = state.worker_rank
115
+ assert dst < num_ranks
116
+ shard_elems = split_elems_for_src(p, state, rank, num_ranks)
117
+ g = p.grad
118
+ g = g.to_local().to(COMM_DTYPE).contiguous().view(-1)
119
+ assert g.numel() == shard_elems
120
+ per_dst[dst].append(g)
121
+ send_counts[dst] += shard_elems
122
+
123
+ assert all(
124
+ len(v) > 0
125
+ for v in per_dst), "all params should be sharded to all devices"
126
+
127
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
128
+ owner_params = [
129
+ p for p in params if param_to_state[id(p)].worker_rank == rank
130
+ ]
131
+
132
+ # Calculate receiving tensors
133
+ recv_counts = [0] * num_ranks
134
+ for src in range(num_ranks):
135
+ total = 0
136
+ for p in owner_params:
137
+ state = param_to_state[id(p)]
138
+ assert state.worker_rank == rank
139
+ total += split_elems_for_src(p, state, src, num_ranks)
140
+ recv_counts[src] = total
141
+
142
+ recv_total = sum(recv_counts)
143
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
144
+ dist.all_to_all_single(
145
+ recv_buf,
146
+ send_buf,
147
+ output_split_sizes=recv_counts,
148
+ input_split_sizes=send_counts,
149
+ group=process_group,
150
  )
151
+
152
+ # Reconstructs gathered grad from the received buffer
153
+ #
154
+ # recv_buf (num ranks = 3)
155
+ #
156
+ # From rank 0 From rank 1 From rank 2
157
+ # | p1_0, p2_0, p3_0 | p1_1, p2_1, p3_1 | p1_2, p2_2, p3_2 |
158
+ #
159
+ # Outer loop:
160
+ # rank 0 -> rank 1 -> rank2
161
+ #
162
+ # Inner loop:
163
+ # p1_n -> p2_n -> p3_n
164
+
165
+ comm_stream.wait_event(alloc_event)
166
+
167
+ off = 0
168
+ write_offsets = {id(p): 0 for p in owner_params}
169
+ for src in range(num_ranks):
170
+ if recv_counts[src] == 0:
171
+ continue
172
+
173
+ block = recv_counts[src]
174
+ inner_off = 0
175
+ for p in owner_params:
176
+ state = param_to_state[id(p)]
177
+ assert state.worker_rank == rank
178
+ n = split_elems_for_src(p, state, src, num_ranks)
179
+ assert n > 0
180
+
181
+ sg = recv_buf.narrow(0, off + inner_off, n)
182
+ woff = write_offsets[id(p)]
183
+ dst = state.gathered_grad.narrow(0, woff, n)
184
+ dst.copy_(sg)
185
+
186
+ write_offsets[id(p)] += n
187
+ inner_off += n
188
+ off += block
189
+
190
+ for p in params:
191
+ state = param_to_state[id(p)]
192
+ if state.worker_rank == rank:
193
+ state.gathered_grad = state.gathered_grad.view_as(p)
194
+ state.gather_event = torch.cuda.Event()
195
+ state.gather_event.record(comm_stream)
196
+ else:
197
+ state.gathered_grad = None
198
+ state.gather_event = None
199
+ if none_grad:
200
+ p.grad = None
201
 
202
 
203
  @torch.no_grad()
 
211
  raise RuntimeError("Gather event must be set before compute.")
212
  compute_stream.wait_event(state.gather_event)
213
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
214
+ state.gathered_grad = None
215
  state.computed_u = u
216
+ state.compute_event = torch.cuda.Event()
217
+ state.compute_event.record()
218
+ else:
219
+ state.computed_u = None
220
+ state.compute_event = None
221
 
222
 
223
  @torch.no_grad()
224
+ def _alloc_scattered_u(params, param_to_state, rank, compute_stream):
225
+ with torch.cuda.stream(compute_stream):
226
+ for p in params:
227
+ state = param_to_state[id(p)]
228
+ state.scattered_u = torch.empty_like(p.to_local(),
229
+ dtype=COMM_DTYPE)
230
+
231
+ alloc_event = torch.cuda.Event()
232
+ alloc_event.record(compute_stream)
233
+ return alloc_event
234
 
235
+
236
+ def _all2all_scatter(params, param_to_state, rank, comm_stream, alloc_event):
237
  with torch.cuda.stream(comm_stream):
238
+ process_group = param_to_state[id(params[0])].process_group
239
+ num_ranks = dist.get_world_size(group=process_group)
240
+ owner_params = [
241
+ p for p in params if param_to_state[id(p)].worker_rank == rank
242
+ ]
243
 
244
+ per_dst = [[] for _ in range(num_ranks)]
245
+ send_counts = [0] * num_ranks
 
 
246
 
247
+ if owner_params:
248
+ for p in owner_params:
249
+ state = param_to_state[id(p)]
250
+ if state.compute_event is None:
251
+ raise RuntimeError(
252
+ "Compute event must be set before scatter.")
253
+ comm_stream.wait_event(state.compute_event)
254
+ state.gathered_grad = None
255
 
256
+ assert state.computed_u is not None
257
+
258
+ u_full = state.computed_u.to(COMM_DTYPE).contiguous().view(-1)
259
+
260
+ offset = 0
261
+ for dst in range(num_ranks):
262
+ n = split_elems_for_src(p, state, dst, num_ranks)
263
+ assert n > 0
264
+
265
+ su = u_full.narrow(0, offset, n)
266
+ per_dst[dst].append(su)
267
+ send_counts[dst] += n
268
+ offset += n
269
+
270
+ assert offset == u_full.numel()
271
+
272
+ if any(len(v) > 0 for v in per_dst):
273
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
274
+ else:
275
+ # all_to_all requires participation from all ranks
276
+ # Even non-owner ranks must join the collective call
277
+ send_buf = torch.empty(0, dtype=COMM_DTYPE, device="cuda")
278
+
279
+ recv_counts = [0] * num_ranks
280
+ for src in range(num_ranks):
281
+ total = 0
282
+ for p in params:
283
+ state = param_to_state[id(p)]
284
+ if state.worker_rank != src:
285
+ continue
286
+ total += split_elems_for_src(p, state, rank, num_ranks)
287
+ recv_counts[src] = total
288
+
289
+ recv_total = sum(recv_counts)
290
+ assert recv_total > 0
291
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
292
+
293
+ dist.all_to_all_single(
294
+ recv_buf,
295
+ send_buf,
296
+ output_split_sizes=recv_counts,
297
+ input_split_sizes=send_counts,
298
+ group=process_group,
299
  )
300
+
301
+ comm_stream.wait_event(alloc_event)
302
+
303
+ off = 0
304
+ for src in range(num_ranks):
305
+ block = recv_counts[src]
306
+ if block == 0:
307
+ continue
308
+
309
+ inner_off = 0
310
+ for p in params:
311
+ state = param_to_state[id(p)]
312
+ if state.worker_rank != src:
313
+ continue
314
+ n = split_elems_for_src(p, state, rank, num_ranks)
315
+ assert n > 0
316
+
317
+ flat_local = recv_buf.narrow(0, off + inner_off,
318
+ n).view_as(p.to_local())
319
+ state.scattered_u.copy_(flat_local)
320
+
321
+ state.scatter_event = torch.cuda.Event()
322
+ state.scatter_event.record(comm_stream)
323
+ inner_off += n
324
+
325
+ assert inner_off == block
326
+ off += block
327
 
328
 
329
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
 
602
  if mesh is None:
603
  mesh = p.device_mesh
604
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
605
+ self.rank = dist.get_rank(group=process_group)
606
  elif mesh != p.device_mesh:
607
  raise ValueError("All parameters must be on the same mesh.")
608
 
609
+ num_ranks = dist.get_world_size(group=process_group)
610
  param_to_state[id(p)] = _muon_state()
611
+ param_to_state[id(
612
+ p)].worker_rank = shard_mesh[round_robin].item() % num_ranks
613
  param_to_state[id(p)].process_group = process_group
614
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
615
  param_to_state[id(p)].qk_clip_state = qk_clip_state
 
639
  else:
640
  g = buf
641
 
642
+ u = _zeropower_via_newtonschulz5(g.to(COMM_DTYPE),
643
  steps=group["ns_steps"])
644
 
645
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
 
654
  def _update_g(self, p, g, group, momentum):
655
  # calc update
656
  state = self.state[p]
657
+ buf = state.setdefault("momentum_buffer", torch.zeros_like(g))
658
+ torch.add(g, buf, alpha=momentum, out=buf)
 
 
659
  if group["nesterov"]:
660
+ g.add_(buf, alpha=momentum)
661
+ return g
662
+ return buf
 
663
 
664
  @staticmethod
665
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
 
743
  param_to_state, ordered_params = self.init_state_and_assign_params(
744
  names, params, group, qk_logits)
745
 
746
+ def enqueue_all2all_gather(start_idx, chunk_size):
747
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
748
+ if target_params:
749
+ alloc_event = _alloc_gathered_grad(target_params,
750
+ param_to_state, self.rank,
751
+ self.compute_stream)
752
+ _all2all_gather(target_params, param_to_state, self.rank,
753
+ self.comm_stream, group["none_grad"],
754
+ alloc_event)
755
 
756
  def enqueue_computes(start_idx, chunk_size):
757
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
759
  _compute_u(p, state, group["ns_steps"], self.rank,
760
  self.compute_stream)
761
 
762
+ def enqueue_all2all_scatter(start_idx, chunk_size):
763
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
764
+ if target_params:
765
+ alloc_event = _alloc_scattered_u(target_params, param_to_state,
766
+ self.rank,
767
+ self.compute_stream)
768
+ _all2all_scatter(target_params, param_to_state, self.rank,
769
+ self.comm_stream, alloc_event)
770
 
771
  def enqueue_update_param(start_idx, chunk_size):
772
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
781
  # Wait grad update
782
  self.comm_stream.wait_stream(torch.cuda.current_stream())
783
 
784
+ PRE_STEP = 5
785
+ for i in range(0, PRE_STEP):
786
+ enqueue_all2all_gather(i * chunk_size, chunk_size)
787
+ enqueue_computes(i * chunk_size, chunk_size)
788
+
789
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
790
+ enqueue_all2all_scatter(i, chunk_size)
791
+ enqueue_all2all_gather(i + PRE_STEP * chunk_size, chunk_size)
792
+ enqueue_update_param(i, chunk_size)
793
+ enqueue_computes(i + PRE_STEP * chunk_size, chunk_size)
 
 
794
 
795
  # Wait the last update_param to finish
796
  torch.cuda.current_stream().wait_stream(self.compute_stream)
build/torch27-cxx11-rocm63-x86_64-linux/optimizer/_ops.py CHANGED
@@ -1,9 +1,9 @@
1
  import torch
2
- from . import _optimizer_9c21645_dirty
3
- ops = torch.ops._optimizer_9c21645_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
- return f"_optimizer_9c21645_dirty::{op_name}"
 
1
  import torch
2
+ from . import _optimizer_fceb334_dirty
3
+ ops = torch.ops._optimizer_fceb334_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
+ return f"_optimizer_fceb334_dirty::{op_name}"
build/torch27-cxx11-rocm63-x86_64-linux/optimizer/{_optimizer_9c21645_dirty.abi3.so → _optimizer_fceb334_dirty.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:41492cb1479920b654768a5597d88670dd0caeedbdcd73fd63afa31ffc6961d6
3
  size 1749776
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:083283a8bd6037ed40c50acc478806786547298abe51a9494bcac0e468ca09fd
3
  size 1749776
build/torch27-cxx11-rocm63-x86_64-linux/optimizer/matmul_transpose_triton.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import triton
3
+ import triton.language as tl
4
+
5
+
6
+ def get_autotune_config():
7
+ return [
8
+ triton.Config(
9
+ {
10
+ 'BLOCK_SIZE_M': blk_m,
11
+ 'BLOCK_SIZE_K': blk_k,
12
+ 'GROUP_SIZE_M': grp_sz
13
+ },
14
+ num_stages=n_stages,
15
+ num_warps=n_warps) for blk_m in [32, 64, 128]
16
+ for blk_k in [32, 64] for grp_sz in [8] for n_stages in [3, 4, 5]
17
+ for n_warps in [4, 8]
18
+ ]
19
+
20
+
21
+ @triton.autotune(
22
+ configs=get_autotune_config(),
23
+ key=['M', 'K'],
24
+ )
25
+ @triton.jit
26
+ def mmt_kernel(x, y, M, K, stride_xm, stride_xk, stride_ym, stride_yn,
27
+ BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_K: tl.constexpr,
28
+ GROUP_SIZE_M: tl.constexpr):
29
+ """
30
+ Core kernel jit function of matmul_transpose that computes y = x @ x.T
31
+ The code is a simple adaptation from the triton `matmul` tutorial:
32
+ https://triton-lang.org/main/getting-started/tutorials/03-matrix-multiplication.html
33
+ """
34
+ pid = tl.program_id(axis=0)
35
+ num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
36
+ num_pid_n = tl.cdiv(M, BLOCK_SIZE_M)
37
+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
38
+ group_id = pid // num_pid_in_group
39
+ first_pid_m = group_id * GROUP_SIZE_M
40
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
41
+ pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
42
+ pid_n = (pid % num_pid_in_group) // group_size_m
43
+ if pid_m > pid_n:
44
+ return
45
+
46
+ offs_xm = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
47
+ offs_xn = (pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
48
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
49
+ # we use a & b ptrs to denote different rows of x.
50
+ a_ptrs = x + (offs_xm[:, None] * stride_xm + offs_k[None, :] * stride_xk)
51
+ b_ptrs = x + (offs_xn[:, None] * stride_xm + offs_k[None, :] * stride_xk)
52
+
53
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_M), dtype=tl.float32)
54
+
55
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
56
+ a = tl.load(a_ptrs,
57
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
58
+ other=0.0)
59
+ b = tl.load(b_ptrs,
60
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
61
+ other=0.0)
62
+ accumulator = tl.dot(a, tl.permute(b, (1, 0)), accumulator)
63
+ a_ptrs += BLOCK_SIZE_K * stride_xk
64
+ b_ptrs += BLOCK_SIZE_K * stride_xk
65
+ # use dtype.element_ty to accommodate different input datatypes as in cpp templates
66
+ # https://github.com/triton-lang/triton/issues/2252
67
+ c = accumulator.to(x.dtype.element_ty)
68
+
69
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
70
+ offs_cn = pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
71
+ c_ptrs = y + stride_ym * offs_cm[:, None] + stride_yn * offs_cn[None, :]
72
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < M)
73
+ tl.store(c_ptrs, c, mask=c_mask)
74
+
75
+ # transpose and copy
76
+ if pid_m < pid_n:
77
+ ct_ptrs = y + stride_ym * offs_cn[:,
78
+ None] + stride_yn * offs_cm[None, :]
79
+ ct_mask = (offs_cn[:, None] < M) & (offs_cm[None, :] < M)
80
+ tl.store(ct_ptrs, tl.permute(c, (1, 0)), mask=ct_mask)
81
+
82
+
83
+ def matmul_transpose_assign(d_in, d_out):
84
+ assert d_in.is_cuda, "Input `d_in` must be a CUDA tensor"
85
+ assert d_out.is_cuda, "Input `d_out` must be a CUDA tensor"
86
+ assert d_in.device == d_out.device, "Inputs `d_in` and `d_out` must be on the same CUDA device"
87
+ assert d_in.dtype == d_out.dtype, "Inputs must have the same data type"
88
+ assert d_in.ndim == 2, "Input `d_in` must be a 2D tensor"
89
+ assert d_out.ndim == 2, "Input `d_out` must be a 2D tensor"
90
+ assert d_in.size(0) == d_out.size(0) == d_out.size(0), \
91
+ "First dimension of `d_in` must match first and second dimension of `d_out`"
92
+
93
+ d_in = d_in.contiguous()
94
+ M, K = d_in.shape
95
+ grid = lambda META: (triton.cdiv(M, META['BLOCK_SIZE_M']) * triton.cdiv(
96
+ M, META['BLOCK_SIZE_M']), )
97
+ with torch.cuda.device(d_in.device.index):
98
+ mmt_kernel[grid](d_in, d_out, M, K, d_in.stride(0), d_in.stride(1),
99
+ d_out.stride(0), d_out.stride(1))
100
+
101
+
102
+ def matmul_transpose(d_in):
103
+ M, _ = d_in.shape
104
+ d_out = torch.empty((M, M), device=d_in.device, dtype=d_in.dtype)
105
+ matmul_transpose_assign(d_in, d_out)
106
+ return d_out
build/torch27-cxx11-rocm63-x86_64-linux/optimizer/muon.py CHANGED
@@ -8,14 +8,19 @@ import torch
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
 
 
11
  logger = logging.getLogger(__name__)
12
 
 
 
13
 
14
  # This code snippet is a modified version adapted from the following GitHub repositories:
15
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
16
  # Muon's Newton–Schulz iteration causes high variance in singular values
17
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
18
  @torch.no_grad()
 
19
  def _zeropower_via_newtonschulz5(G, steps):
20
  """
21
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
@@ -27,13 +32,15 @@ def _zeropower_via_newtonschulz5(G, steps):
27
  performance at all relative to UV^T, where USV^T = G is the SVD.
28
  """
29
  assert len(G.shape) == 2
30
- assert G.dtype == torch.bfloat16
31
  X = G # no manual typecast
32
 
33
  if G.size(0) > G.size(1):
34
  X = X.T
35
  # Ensure spectral norm is at most 1
36
  X = X / (X.norm() + 1e-7)
 
 
37
  # Perform the NS iterations
38
  for a, b, c in [
39
  (4.0848, -6.8946, 2.9270),
@@ -42,13 +49,10 @@ def _zeropower_via_newtonschulz5(G, steps):
42
  (2.8769, -3.1427, 1.2046),
43
  (2.8366, -3.0525, 1.2012),
44
  ]:
45
- A = X @ X.T
46
- # B = (
47
- # b * A + c * A @ A
48
- # )
49
- B = torch.addmm(A, A, A, alpha=c, beta=b)
50
- # X = a * X + B @ X
51
- X = torch.addmm(X, B, X, alpha=1.0, beta=a)
52
 
53
  if G.size(0) > G.size(1):
54
  X = X.T
@@ -69,51 +73,131 @@ class _muon_state:
69
  qk_clip_state = None
70
 
71
 
 
 
 
 
 
 
 
 
72
  @torch.no_grad()
73
- def _gather(p, state, rank, comm_stream, none_grad):
74
- """
75
- Gather the gradients to worker_rank.
76
- If none_grad is True, free p.grad after the gather.
77
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  with torch.cuda.stream(comm_stream):
79
- g = p.grad
 
80
 
81
- if rank == state.worker_rank:
82
- num_ranks = dist.get_world_size(group=state.process_group)
83
- gather_list = [
84
- torch.empty_like(g.to_local(), dtype=torch.bfloat16)
85
- for _ in range(num_ranks)
86
- ]
87
- else:
88
- gather_list = None
89
-
90
- g = g.to(torch.bfloat16)
91
- torch.distributed.gather(
92
- g.to_local(),
93
- dst=state.worker_rank,
94
- gather_list=gather_list,
95
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  )
97
- if rank == state.worker_rank:
98
- if state.gathered_grad is not None:
99
- raise RuntimeError(
100
- "Gather event already exists, which should not happen.")
101
- state.gathered_grad = torch.cat(gather_list, dim=0)
102
- state.gather_event = torch.cuda.Event()
103
- state.gather_event.record()
104
- else:
105
- state.gathered_grad = None
106
- state.gather_event = None
107
- gather_list = None
108
- if none_grad:
109
- # We can safely free p.grad without calling record_stream:
110
- # p.grad.to_local().record_stream(comm_stream)
111
- # Explanation:
112
- # 1. p.grad is created on the default stream, but the default stream
113
- # is synchronized with the comm stream later.
114
- # 2. There is no further activity on the default stream before the optimizer finishes.
115
- # Therefore, it is safe to free p.grad directly on the comm stream.
116
- p.grad = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
 
119
  @torch.no_grad()
@@ -127,45 +211,119 @@ def _compute_u(p, state, steps, rank, compute_stream):
127
  raise RuntimeError("Gather event must be set before compute.")
128
  compute_stream.wait_event(state.gather_event)
129
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
 
130
  state.computed_u = u
131
- state.scattered_u = torch.empty_like(p.to_local(),
132
- dtype=torch.bfloat16)
133
- state.compute_event = torch.cuda.Event()
134
- state.compute_event.record()
135
- u = None
136
 
137
 
138
  @torch.no_grad()
139
- def _scatter(p, state, rank, comm_stream):
140
- """
141
- Scatter the computed_u from worker_rank to all ranks.
142
- """
 
 
 
 
 
 
143
 
 
 
144
  with torch.cuda.stream(comm_stream):
145
- if state.compute_event is None:
146
- raise RuntimeError("Compute event must be set before scatter.")
147
- comm_stream.wait_event(state.compute_event)
 
 
148
 
149
- if rank == state.worker_rank:
150
- num_ranks = dist.get_world_size(group=state.process_group)
151
- # Clear the gathered gradient to free memory
152
- state.gathered_grad = None
153
 
154
- u = state.computed_u
155
- scatter_list = list(torch.split(u, p.size(0) // num_ranks, dim=0))
156
- scatter_list = [s.contiguous() for s in scatter_list]
157
- else:
158
- scatter_list = None
 
 
 
159
 
160
- torch.distributed.scatter(
161
- state.scattered_u,
162
- scatter_list=scatter_list,
163
- src=state.worker_rank,
164
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  )
166
- state.scatter_event = torch.cuda.Event()
167
- state.scatter_event.record()
168
- scatter_list = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
 
171
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
@@ -444,11 +602,14 @@ class Muon(torch.optim.Optimizer):
444
  if mesh is None:
445
  mesh = p.device_mesh
446
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
 
447
  elif mesh != p.device_mesh:
448
  raise ValueError("All parameters must be on the same mesh.")
449
 
 
450
  param_to_state[id(p)] = _muon_state()
451
- param_to_state[id(p)].worker_rank = shard_mesh[round_robin].item()
 
452
  param_to_state[id(p)].process_group = process_group
453
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
454
  param_to_state[id(p)].qk_clip_state = qk_clip_state
@@ -478,7 +639,7 @@ class Muon(torch.optim.Optimizer):
478
  else:
479
  g = buf
480
 
481
- u = _zeropower_via_newtonschulz5(g.bfloat16(),
482
  steps=group["ns_steps"])
483
 
484
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
@@ -493,15 +654,12 @@ class Muon(torch.optim.Optimizer):
493
  def _update_g(self, p, g, group, momentum):
494
  # calc update
495
  state = self.state[p]
496
- if "momentum_buffer" not in state:
497
- state["momentum_buffer"] = torch.zeros_like(g)
498
- buf = state["momentum_buffer"]
499
- buf.mul_(momentum).add_(g)
500
  if group["nesterov"]:
501
- g = g.add(buf, alpha=momentum)
502
- else:
503
- g = buf
504
- return g
505
 
506
  @staticmethod
507
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
@@ -585,11 +743,15 @@ class Muon(torch.optim.Optimizer):
585
  param_to_state, ordered_params = self.init_state_and_assign_params(
586
  names, params, group, qk_logits)
587
 
588
- def enqueue_gathers(start_idx, chunk_size):
589
- for p in ordered_params[start_idx:start_idx + chunk_size]:
590
- state = param_to_state[id(p)]
591
- _gather(p, state, self.rank, self.comm_stream,
592
- group["none_grad"])
 
 
 
 
593
 
594
  def enqueue_computes(start_idx, chunk_size):
595
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -597,10 +759,14 @@ class Muon(torch.optim.Optimizer):
597
  _compute_u(p, state, group["ns_steps"], self.rank,
598
  self.compute_stream)
599
 
600
- def enqueue_scatters(start_idx, chunk_size):
601
- for p in ordered_params[start_idx:start_idx + chunk_size]:
602
- state = param_to_state[id(p)]
603
- _scatter(p, state, self.rank, self.comm_stream)
 
 
 
 
604
 
605
  def enqueue_update_param(start_idx, chunk_size):
606
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -615,14 +781,16 @@ class Muon(torch.optim.Optimizer):
615
  # Wait grad update
616
  self.comm_stream.wait_stream(torch.cuda.current_stream())
617
 
618
- enqueue_gathers(0, chunk_size)
 
 
 
 
619
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
620
- enqueue_computes(i, chunk_size)
621
- if i > 0:
622
- enqueue_update_param(i - chunk_size, chunk_size)
623
- enqueue_gathers(i + chunk_size, chunk_size)
624
- enqueue_scatters(i, chunk_size)
625
- enqueue_update_param(i, chunk_size)
626
 
627
  # Wait the last update_param to finish
628
  torch.cuda.current_stream().wait_stream(self.compute_stream)
 
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
11
+ from .matmul_transpose_triton import matmul_transpose_assign
12
+
13
  logger = logging.getLogger(__name__)
14
 
15
+ COMM_DTYPE = torch.bfloat16
16
+
17
 
18
  # This code snippet is a modified version adapted from the following GitHub repositories:
19
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
20
  # Muon's Newton–Schulz iteration causes high variance in singular values
21
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
22
  @torch.no_grad()
23
+ # matmul_transpose_assign from : https://github.com/nil0x9/flash-muon
24
  def _zeropower_via_newtonschulz5(G, steps):
25
  """
26
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
 
32
  performance at all relative to UV^T, where USV^T = G is the SVD.
33
  """
34
  assert len(G.shape) == 2
35
+ assert G.dtype == COMM_DTYPE
36
  X = G # no manual typecast
37
 
38
  if G.size(0) > G.size(1):
39
  X = X.T
40
  # Ensure spectral norm is at most 1
41
  X = X / (X.norm() + 1e-7)
42
+ buf1 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
43
+ buf2 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
44
  # Perform the NS iterations
45
  for a, b, c in [
46
  (4.0848, -6.8946, 2.9270),
 
49
  (2.8769, -3.1427, 1.2046),
50
  (2.8366, -3.0525, 1.2012),
51
  ]:
52
+ matmul_transpose_assign(X, buf1)
53
+ matmul_transpose_assign(buf1, buf2)
54
+ buf1.mul_(b).add_(buf2, alpha=c)
55
+ X = torch.addmm(X, buf1, X, alpha=1.0, beta=a)
 
 
 
56
 
57
  if G.size(0) > G.size(1):
58
  X = X.T
 
73
  qk_clip_state = None
74
 
75
 
76
+ def split_elems_for_src(param, state, src_rank, num_ranks) -> int:
77
+ rows = param.shape[0]
78
+ cols = int(param.numel() // rows)
79
+ base, rem = divmod(rows, num_ranks)
80
+ my_rows = base + (1 if src_rank < rem else 0)
81
+ return my_rows * cols
82
+
83
+
84
  @torch.no_grad()
85
+ def _alloc_gathered_grad(params, param_to_state, rank, compute_stream):
86
+ with torch.cuda.stream(compute_stream):
87
+ for p in params:
88
+ state = param_to_state[id(p)]
89
+ if rank == state.worker_rank:
90
+ num_ranks = dist.get_world_size(group=state.process_group)
91
+ state.gathered_grad = torch.empty(p.grad.numel(),
92
+ dtype=COMM_DTYPE,
93
+ device="cuda")
94
+ else:
95
+ state.gathered_grad = None
96
+
97
+ alloc_event = torch.cuda.Event()
98
+ alloc_event.record(compute_stream)
99
+ return alloc_event
100
+
101
+
102
+ @torch.no_grad()
103
+ def _all2all_gather(params, param_to_state, rank, comm_stream, none_grad,
104
+ alloc_event):
105
  with torch.cuda.stream(comm_stream):
106
+ process_group = param_to_state[id(params[0])].process_group
107
+ num_ranks = dist.get_world_size(group=process_group)
108
 
109
+ # Calculate sending tensors
110
+ per_dst = [[] for _ in range(num_ranks)]
111
+ send_counts = [0] * num_ranks
112
+ for p in params:
113
+ state = param_to_state[id(p)]
114
+ dst = state.worker_rank
115
+ assert dst < num_ranks
116
+ shard_elems = split_elems_for_src(p, state, rank, num_ranks)
117
+ g = p.grad
118
+ g = g.to_local().to(COMM_DTYPE).contiguous().view(-1)
119
+ assert g.numel() == shard_elems
120
+ per_dst[dst].append(g)
121
+ send_counts[dst] += shard_elems
122
+
123
+ assert all(
124
+ len(v) > 0
125
+ for v in per_dst), "all params should be sharded to all devices"
126
+
127
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
128
+ owner_params = [
129
+ p for p in params if param_to_state[id(p)].worker_rank == rank
130
+ ]
131
+
132
+ # Calculate receiving tensors
133
+ recv_counts = [0] * num_ranks
134
+ for src in range(num_ranks):
135
+ total = 0
136
+ for p in owner_params:
137
+ state = param_to_state[id(p)]
138
+ assert state.worker_rank == rank
139
+ total += split_elems_for_src(p, state, src, num_ranks)
140
+ recv_counts[src] = total
141
+
142
+ recv_total = sum(recv_counts)
143
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
144
+ dist.all_to_all_single(
145
+ recv_buf,
146
+ send_buf,
147
+ output_split_sizes=recv_counts,
148
+ input_split_sizes=send_counts,
149
+ group=process_group,
150
  )
151
+
152
+ # Reconstructs gathered grad from the received buffer
153
+ #
154
+ # recv_buf (num ranks = 3)
155
+ #
156
+ # From rank 0 From rank 1 From rank 2
157
+ # | p1_0, p2_0, p3_0 | p1_1, p2_1, p3_1 | p1_2, p2_2, p3_2 |
158
+ #
159
+ # Outer loop:
160
+ # rank 0 -> rank 1 -> rank2
161
+ #
162
+ # Inner loop:
163
+ # p1_n -> p2_n -> p3_n
164
+
165
+ comm_stream.wait_event(alloc_event)
166
+
167
+ off = 0
168
+ write_offsets = {id(p): 0 for p in owner_params}
169
+ for src in range(num_ranks):
170
+ if recv_counts[src] == 0:
171
+ continue
172
+
173
+ block = recv_counts[src]
174
+ inner_off = 0
175
+ for p in owner_params:
176
+ state = param_to_state[id(p)]
177
+ assert state.worker_rank == rank
178
+ n = split_elems_for_src(p, state, src, num_ranks)
179
+ assert n > 0
180
+
181
+ sg = recv_buf.narrow(0, off + inner_off, n)
182
+ woff = write_offsets[id(p)]
183
+ dst = state.gathered_grad.narrow(0, woff, n)
184
+ dst.copy_(sg)
185
+
186
+ write_offsets[id(p)] += n
187
+ inner_off += n
188
+ off += block
189
+
190
+ for p in params:
191
+ state = param_to_state[id(p)]
192
+ if state.worker_rank == rank:
193
+ state.gathered_grad = state.gathered_grad.view_as(p)
194
+ state.gather_event = torch.cuda.Event()
195
+ state.gather_event.record(comm_stream)
196
+ else:
197
+ state.gathered_grad = None
198
+ state.gather_event = None
199
+ if none_grad:
200
+ p.grad = None
201
 
202
 
203
  @torch.no_grad()
 
211
  raise RuntimeError("Gather event must be set before compute.")
212
  compute_stream.wait_event(state.gather_event)
213
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
214
+ state.gathered_grad = None
215
  state.computed_u = u
216
+ state.compute_event = torch.cuda.Event()
217
+ state.compute_event.record()
218
+ else:
219
+ state.computed_u = None
220
+ state.compute_event = None
221
 
222
 
223
  @torch.no_grad()
224
+ def _alloc_scattered_u(params, param_to_state, rank, compute_stream):
225
+ with torch.cuda.stream(compute_stream):
226
+ for p in params:
227
+ state = param_to_state[id(p)]
228
+ state.scattered_u = torch.empty_like(p.to_local(),
229
+ dtype=COMM_DTYPE)
230
+
231
+ alloc_event = torch.cuda.Event()
232
+ alloc_event.record(compute_stream)
233
+ return alloc_event
234
 
235
+
236
+ def _all2all_scatter(params, param_to_state, rank, comm_stream, alloc_event):
237
  with torch.cuda.stream(comm_stream):
238
+ process_group = param_to_state[id(params[0])].process_group
239
+ num_ranks = dist.get_world_size(group=process_group)
240
+ owner_params = [
241
+ p for p in params if param_to_state[id(p)].worker_rank == rank
242
+ ]
243
 
244
+ per_dst = [[] for _ in range(num_ranks)]
245
+ send_counts = [0] * num_ranks
 
 
246
 
247
+ if owner_params:
248
+ for p in owner_params:
249
+ state = param_to_state[id(p)]
250
+ if state.compute_event is None:
251
+ raise RuntimeError(
252
+ "Compute event must be set before scatter.")
253
+ comm_stream.wait_event(state.compute_event)
254
+ state.gathered_grad = None
255
 
256
+ assert state.computed_u is not None
257
+
258
+ u_full = state.computed_u.to(COMM_DTYPE).contiguous().view(-1)
259
+
260
+ offset = 0
261
+ for dst in range(num_ranks):
262
+ n = split_elems_for_src(p, state, dst, num_ranks)
263
+ assert n > 0
264
+
265
+ su = u_full.narrow(0, offset, n)
266
+ per_dst[dst].append(su)
267
+ send_counts[dst] += n
268
+ offset += n
269
+
270
+ assert offset == u_full.numel()
271
+
272
+ if any(len(v) > 0 for v in per_dst):
273
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
274
+ else:
275
+ # all_to_all requires participation from all ranks
276
+ # Even non-owner ranks must join the collective call
277
+ send_buf = torch.empty(0, dtype=COMM_DTYPE, device="cuda")
278
+
279
+ recv_counts = [0] * num_ranks
280
+ for src in range(num_ranks):
281
+ total = 0
282
+ for p in params:
283
+ state = param_to_state[id(p)]
284
+ if state.worker_rank != src:
285
+ continue
286
+ total += split_elems_for_src(p, state, rank, num_ranks)
287
+ recv_counts[src] = total
288
+
289
+ recv_total = sum(recv_counts)
290
+ assert recv_total > 0
291
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
292
+
293
+ dist.all_to_all_single(
294
+ recv_buf,
295
+ send_buf,
296
+ output_split_sizes=recv_counts,
297
+ input_split_sizes=send_counts,
298
+ group=process_group,
299
  )
300
+
301
+ comm_stream.wait_event(alloc_event)
302
+
303
+ off = 0
304
+ for src in range(num_ranks):
305
+ block = recv_counts[src]
306
+ if block == 0:
307
+ continue
308
+
309
+ inner_off = 0
310
+ for p in params:
311
+ state = param_to_state[id(p)]
312
+ if state.worker_rank != src:
313
+ continue
314
+ n = split_elems_for_src(p, state, rank, num_ranks)
315
+ assert n > 0
316
+
317
+ flat_local = recv_buf.narrow(0, off + inner_off,
318
+ n).view_as(p.to_local())
319
+ state.scattered_u.copy_(flat_local)
320
+
321
+ state.scatter_event = torch.cuda.Event()
322
+ state.scatter_event.record(comm_stream)
323
+ inner_off += n
324
+
325
+ assert inner_off == block
326
+ off += block
327
 
328
 
329
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
 
602
  if mesh is None:
603
  mesh = p.device_mesh
604
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
605
+ self.rank = dist.get_rank(group=process_group)
606
  elif mesh != p.device_mesh:
607
  raise ValueError("All parameters must be on the same mesh.")
608
 
609
+ num_ranks = dist.get_world_size(group=process_group)
610
  param_to_state[id(p)] = _muon_state()
611
+ param_to_state[id(
612
+ p)].worker_rank = shard_mesh[round_robin].item() % num_ranks
613
  param_to_state[id(p)].process_group = process_group
614
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
615
  param_to_state[id(p)].qk_clip_state = qk_clip_state
 
639
  else:
640
  g = buf
641
 
642
+ u = _zeropower_via_newtonschulz5(g.to(COMM_DTYPE),
643
  steps=group["ns_steps"])
644
 
645
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
 
654
  def _update_g(self, p, g, group, momentum):
655
  # calc update
656
  state = self.state[p]
657
+ buf = state.setdefault("momentum_buffer", torch.zeros_like(g))
658
+ torch.add(g, buf, alpha=momentum, out=buf)
 
 
659
  if group["nesterov"]:
660
+ g.add_(buf, alpha=momentum)
661
+ return g
662
+ return buf
 
663
 
664
  @staticmethod
665
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
 
743
  param_to_state, ordered_params = self.init_state_and_assign_params(
744
  names, params, group, qk_logits)
745
 
746
+ def enqueue_all2all_gather(start_idx, chunk_size):
747
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
748
+ if target_params:
749
+ alloc_event = _alloc_gathered_grad(target_params,
750
+ param_to_state, self.rank,
751
+ self.compute_stream)
752
+ _all2all_gather(target_params, param_to_state, self.rank,
753
+ self.comm_stream, group["none_grad"],
754
+ alloc_event)
755
 
756
  def enqueue_computes(start_idx, chunk_size):
757
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
759
  _compute_u(p, state, group["ns_steps"], self.rank,
760
  self.compute_stream)
761
 
762
+ def enqueue_all2all_scatter(start_idx, chunk_size):
763
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
764
+ if target_params:
765
+ alloc_event = _alloc_scattered_u(target_params, param_to_state,
766
+ self.rank,
767
+ self.compute_stream)
768
+ _all2all_scatter(target_params, param_to_state, self.rank,
769
+ self.comm_stream, alloc_event)
770
 
771
  def enqueue_update_param(start_idx, chunk_size):
772
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
781
  # Wait grad update
782
  self.comm_stream.wait_stream(torch.cuda.current_stream())
783
 
784
+ PRE_STEP = 5
785
+ for i in range(0, PRE_STEP):
786
+ enqueue_all2all_gather(i * chunk_size, chunk_size)
787
+ enqueue_computes(i * chunk_size, chunk_size)
788
+
789
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
790
+ enqueue_all2all_scatter(i, chunk_size)
791
+ enqueue_all2all_gather(i + PRE_STEP * chunk_size, chunk_size)
792
+ enqueue_update_param(i, chunk_size)
793
+ enqueue_computes(i + PRE_STEP * chunk_size, chunk_size)
 
 
794
 
795
  # Wait the last update_param to finish
796
  torch.cuda.current_stream().wait_stream(self.compute_stream)
build/torch28-cxx11-cu126-x86_64-linux/optimizer/_ops.py CHANGED
@@ -1,9 +1,9 @@
1
  import torch
2
- from . import _optimizer_9c21645_dirty
3
- ops = torch.ops._optimizer_9c21645_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
- return f"_optimizer_9c21645_dirty::{op_name}"
 
1
  import torch
2
+ from . import _optimizer_fceb334_dirty
3
+ ops = torch.ops._optimizer_fceb334_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
+ return f"_optimizer_fceb334_dirty::{op_name}"
build/{torch27-cxx11-cu126-x86_64-linux/optimizer/_optimizer_9c21645_dirty.abi3.so → torch28-cxx11-cu126-x86_64-linux/optimizer/_optimizer_fceb334_dirty.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:42ae6ac1cf967d7d23cac7930c8db635105f60631220a60b9cee060d082f40ae
3
  size 1824256
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c31efe0c8bad71c6fbc002f269eff7a30754e1583bb8c892b8ce5cdbdfc5121c
3
  size 1824256
build/torch28-cxx11-cu126-x86_64-linux/optimizer/matmul_transpose_triton.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import triton
3
+ import triton.language as tl
4
+
5
+
6
+ def get_autotune_config():
7
+ return [
8
+ triton.Config(
9
+ {
10
+ 'BLOCK_SIZE_M': blk_m,
11
+ 'BLOCK_SIZE_K': blk_k,
12
+ 'GROUP_SIZE_M': grp_sz
13
+ },
14
+ num_stages=n_stages,
15
+ num_warps=n_warps) for blk_m in [32, 64, 128]
16
+ for blk_k in [32, 64] for grp_sz in [8] for n_stages in [3, 4, 5]
17
+ for n_warps in [4, 8]
18
+ ]
19
+
20
+
21
+ @triton.autotune(
22
+ configs=get_autotune_config(),
23
+ key=['M', 'K'],
24
+ )
25
+ @triton.jit
26
+ def mmt_kernel(x, y, M, K, stride_xm, stride_xk, stride_ym, stride_yn,
27
+ BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_K: tl.constexpr,
28
+ GROUP_SIZE_M: tl.constexpr):
29
+ """
30
+ Core kernel jit function of matmul_transpose that computes y = x @ x.T
31
+ The code is a simple adaptation from the triton `matmul` tutorial:
32
+ https://triton-lang.org/main/getting-started/tutorials/03-matrix-multiplication.html
33
+ """
34
+ pid = tl.program_id(axis=0)
35
+ num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
36
+ num_pid_n = tl.cdiv(M, BLOCK_SIZE_M)
37
+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
38
+ group_id = pid // num_pid_in_group
39
+ first_pid_m = group_id * GROUP_SIZE_M
40
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
41
+ pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
42
+ pid_n = (pid % num_pid_in_group) // group_size_m
43
+ if pid_m > pid_n:
44
+ return
45
+
46
+ offs_xm = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
47
+ offs_xn = (pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
48
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
49
+ # we use a & b ptrs to denote different rows of x.
50
+ a_ptrs = x + (offs_xm[:, None] * stride_xm + offs_k[None, :] * stride_xk)
51
+ b_ptrs = x + (offs_xn[:, None] * stride_xm + offs_k[None, :] * stride_xk)
52
+
53
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_M), dtype=tl.float32)
54
+
55
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
56
+ a = tl.load(a_ptrs,
57
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
58
+ other=0.0)
59
+ b = tl.load(b_ptrs,
60
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
61
+ other=0.0)
62
+ accumulator = tl.dot(a, tl.permute(b, (1, 0)), accumulator)
63
+ a_ptrs += BLOCK_SIZE_K * stride_xk
64
+ b_ptrs += BLOCK_SIZE_K * stride_xk
65
+ # use dtype.element_ty to accommodate different input datatypes as in cpp templates
66
+ # https://github.com/triton-lang/triton/issues/2252
67
+ c = accumulator.to(x.dtype.element_ty)
68
+
69
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
70
+ offs_cn = pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
71
+ c_ptrs = y + stride_ym * offs_cm[:, None] + stride_yn * offs_cn[None, :]
72
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < M)
73
+ tl.store(c_ptrs, c, mask=c_mask)
74
+
75
+ # transpose and copy
76
+ if pid_m < pid_n:
77
+ ct_ptrs = y + stride_ym * offs_cn[:,
78
+ None] + stride_yn * offs_cm[None, :]
79
+ ct_mask = (offs_cn[:, None] < M) & (offs_cm[None, :] < M)
80
+ tl.store(ct_ptrs, tl.permute(c, (1, 0)), mask=ct_mask)
81
+
82
+
83
+ def matmul_transpose_assign(d_in, d_out):
84
+ assert d_in.is_cuda, "Input `d_in` must be a CUDA tensor"
85
+ assert d_out.is_cuda, "Input `d_out` must be a CUDA tensor"
86
+ assert d_in.device == d_out.device, "Inputs `d_in` and `d_out` must be on the same CUDA device"
87
+ assert d_in.dtype == d_out.dtype, "Inputs must have the same data type"
88
+ assert d_in.ndim == 2, "Input `d_in` must be a 2D tensor"
89
+ assert d_out.ndim == 2, "Input `d_out` must be a 2D tensor"
90
+ assert d_in.size(0) == d_out.size(0) == d_out.size(0), \
91
+ "First dimension of `d_in` must match first and second dimension of `d_out`"
92
+
93
+ d_in = d_in.contiguous()
94
+ M, K = d_in.shape
95
+ grid = lambda META: (triton.cdiv(M, META['BLOCK_SIZE_M']) * triton.cdiv(
96
+ M, META['BLOCK_SIZE_M']), )
97
+ with torch.cuda.device(d_in.device.index):
98
+ mmt_kernel[grid](d_in, d_out, M, K, d_in.stride(0), d_in.stride(1),
99
+ d_out.stride(0), d_out.stride(1))
100
+
101
+
102
+ def matmul_transpose(d_in):
103
+ M, _ = d_in.shape
104
+ d_out = torch.empty((M, M), device=d_in.device, dtype=d_in.dtype)
105
+ matmul_transpose_assign(d_in, d_out)
106
+ return d_out
build/torch28-cxx11-cu126-x86_64-linux/optimizer/muon.py CHANGED
@@ -8,14 +8,19 @@ import torch
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
 
 
11
  logger = logging.getLogger(__name__)
12
 
 
 
13
 
14
  # This code snippet is a modified version adapted from the following GitHub repositories:
15
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
16
  # Muon's Newton–Schulz iteration causes high variance in singular values
17
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
18
  @torch.no_grad()
 
19
  def _zeropower_via_newtonschulz5(G, steps):
20
  """
21
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
@@ -27,13 +32,15 @@ def _zeropower_via_newtonschulz5(G, steps):
27
  performance at all relative to UV^T, where USV^T = G is the SVD.
28
  """
29
  assert len(G.shape) == 2
30
- assert G.dtype == torch.bfloat16
31
  X = G # no manual typecast
32
 
33
  if G.size(0) > G.size(1):
34
  X = X.T
35
  # Ensure spectral norm is at most 1
36
  X = X / (X.norm() + 1e-7)
 
 
37
  # Perform the NS iterations
38
  for a, b, c in [
39
  (4.0848, -6.8946, 2.9270),
@@ -42,13 +49,10 @@ def _zeropower_via_newtonschulz5(G, steps):
42
  (2.8769, -3.1427, 1.2046),
43
  (2.8366, -3.0525, 1.2012),
44
  ]:
45
- A = X @ X.T
46
- # B = (
47
- # b * A + c * A @ A
48
- # )
49
- B = torch.addmm(A, A, A, alpha=c, beta=b)
50
- # X = a * X + B @ X
51
- X = torch.addmm(X, B, X, alpha=1.0, beta=a)
52
 
53
  if G.size(0) > G.size(1):
54
  X = X.T
@@ -69,51 +73,131 @@ class _muon_state:
69
  qk_clip_state = None
70
 
71
 
 
 
 
 
 
 
 
 
72
  @torch.no_grad()
73
- def _gather(p, state, rank, comm_stream, none_grad):
74
- """
75
- Gather the gradients to worker_rank.
76
- If none_grad is True, free p.grad after the gather.
77
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  with torch.cuda.stream(comm_stream):
79
- g = p.grad
 
80
 
81
- if rank == state.worker_rank:
82
- num_ranks = dist.get_world_size(group=state.process_group)
83
- gather_list = [
84
- torch.empty_like(g.to_local(), dtype=torch.bfloat16)
85
- for _ in range(num_ranks)
86
- ]
87
- else:
88
- gather_list = None
89
-
90
- g = g.to(torch.bfloat16)
91
- torch.distributed.gather(
92
- g.to_local(),
93
- dst=state.worker_rank,
94
- gather_list=gather_list,
95
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  )
97
- if rank == state.worker_rank:
98
- if state.gathered_grad is not None:
99
- raise RuntimeError(
100
- "Gather event already exists, which should not happen.")
101
- state.gathered_grad = torch.cat(gather_list, dim=0)
102
- state.gather_event = torch.cuda.Event()
103
- state.gather_event.record()
104
- else:
105
- state.gathered_grad = None
106
- state.gather_event = None
107
- gather_list = None
108
- if none_grad:
109
- # We can safely free p.grad without calling record_stream:
110
- # p.grad.to_local().record_stream(comm_stream)
111
- # Explanation:
112
- # 1. p.grad is created on the default stream, but the default stream
113
- # is synchronized with the comm stream later.
114
- # 2. There is no further activity on the default stream before the optimizer finishes.
115
- # Therefore, it is safe to free p.grad directly on the comm stream.
116
- p.grad = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
 
119
  @torch.no_grad()
@@ -127,45 +211,119 @@ def _compute_u(p, state, steps, rank, compute_stream):
127
  raise RuntimeError("Gather event must be set before compute.")
128
  compute_stream.wait_event(state.gather_event)
129
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
 
130
  state.computed_u = u
131
- state.scattered_u = torch.empty_like(p.to_local(),
132
- dtype=torch.bfloat16)
133
- state.compute_event = torch.cuda.Event()
134
- state.compute_event.record()
135
- u = None
136
 
137
 
138
  @torch.no_grad()
139
- def _scatter(p, state, rank, comm_stream):
140
- """
141
- Scatter the computed_u from worker_rank to all ranks.
142
- """
 
 
 
 
 
 
143
 
 
 
144
  with torch.cuda.stream(comm_stream):
145
- if state.compute_event is None:
146
- raise RuntimeError("Compute event must be set before scatter.")
147
- comm_stream.wait_event(state.compute_event)
 
 
148
 
149
- if rank == state.worker_rank:
150
- num_ranks = dist.get_world_size(group=state.process_group)
151
- # Clear the gathered gradient to free memory
152
- state.gathered_grad = None
153
 
154
- u = state.computed_u
155
- scatter_list = list(torch.split(u, p.size(0) // num_ranks, dim=0))
156
- scatter_list = [s.contiguous() for s in scatter_list]
157
- else:
158
- scatter_list = None
 
 
 
159
 
160
- torch.distributed.scatter(
161
- state.scattered_u,
162
- scatter_list=scatter_list,
163
- src=state.worker_rank,
164
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  )
166
- state.scatter_event = torch.cuda.Event()
167
- state.scatter_event.record()
168
- scatter_list = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
 
171
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
@@ -444,11 +602,14 @@ class Muon(torch.optim.Optimizer):
444
  if mesh is None:
445
  mesh = p.device_mesh
446
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
 
447
  elif mesh != p.device_mesh:
448
  raise ValueError("All parameters must be on the same mesh.")
449
 
 
450
  param_to_state[id(p)] = _muon_state()
451
- param_to_state[id(p)].worker_rank = shard_mesh[round_robin].item()
 
452
  param_to_state[id(p)].process_group = process_group
453
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
454
  param_to_state[id(p)].qk_clip_state = qk_clip_state
@@ -478,7 +639,7 @@ class Muon(torch.optim.Optimizer):
478
  else:
479
  g = buf
480
 
481
- u = _zeropower_via_newtonschulz5(g.bfloat16(),
482
  steps=group["ns_steps"])
483
 
484
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
@@ -493,15 +654,12 @@ class Muon(torch.optim.Optimizer):
493
  def _update_g(self, p, g, group, momentum):
494
  # calc update
495
  state = self.state[p]
496
- if "momentum_buffer" not in state:
497
- state["momentum_buffer"] = torch.zeros_like(g)
498
- buf = state["momentum_buffer"]
499
- buf.mul_(momentum).add_(g)
500
  if group["nesterov"]:
501
- g = g.add(buf, alpha=momentum)
502
- else:
503
- g = buf
504
- return g
505
 
506
  @staticmethod
507
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
@@ -585,11 +743,15 @@ class Muon(torch.optim.Optimizer):
585
  param_to_state, ordered_params = self.init_state_and_assign_params(
586
  names, params, group, qk_logits)
587
 
588
- def enqueue_gathers(start_idx, chunk_size):
589
- for p in ordered_params[start_idx:start_idx + chunk_size]:
590
- state = param_to_state[id(p)]
591
- _gather(p, state, self.rank, self.comm_stream,
592
- group["none_grad"])
 
 
 
 
593
 
594
  def enqueue_computes(start_idx, chunk_size):
595
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -597,10 +759,14 @@ class Muon(torch.optim.Optimizer):
597
  _compute_u(p, state, group["ns_steps"], self.rank,
598
  self.compute_stream)
599
 
600
- def enqueue_scatters(start_idx, chunk_size):
601
- for p in ordered_params[start_idx:start_idx + chunk_size]:
602
- state = param_to_state[id(p)]
603
- _scatter(p, state, self.rank, self.comm_stream)
 
 
 
 
604
 
605
  def enqueue_update_param(start_idx, chunk_size):
606
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -615,14 +781,16 @@ class Muon(torch.optim.Optimizer):
615
  # Wait grad update
616
  self.comm_stream.wait_stream(torch.cuda.current_stream())
617
 
618
- enqueue_gathers(0, chunk_size)
 
 
 
 
619
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
620
- enqueue_computes(i, chunk_size)
621
- if i > 0:
622
- enqueue_update_param(i - chunk_size, chunk_size)
623
- enqueue_gathers(i + chunk_size, chunk_size)
624
- enqueue_scatters(i, chunk_size)
625
- enqueue_update_param(i, chunk_size)
626
 
627
  # Wait the last update_param to finish
628
  torch.cuda.current_stream().wait_stream(self.compute_stream)
 
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
11
+ from .matmul_transpose_triton import matmul_transpose_assign
12
+
13
  logger = logging.getLogger(__name__)
14
 
15
+ COMM_DTYPE = torch.bfloat16
16
+
17
 
18
  # This code snippet is a modified version adapted from the following GitHub repositories:
19
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
20
  # Muon's Newton–Schulz iteration causes high variance in singular values
21
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
22
  @torch.no_grad()
23
+ # matmul_transpose_assign from : https://github.com/nil0x9/flash-muon
24
  def _zeropower_via_newtonschulz5(G, steps):
25
  """
26
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
 
32
  performance at all relative to UV^T, where USV^T = G is the SVD.
33
  """
34
  assert len(G.shape) == 2
35
+ assert G.dtype == COMM_DTYPE
36
  X = G # no manual typecast
37
 
38
  if G.size(0) > G.size(1):
39
  X = X.T
40
  # Ensure spectral norm is at most 1
41
  X = X / (X.norm() + 1e-7)
42
+ buf1 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
43
+ buf2 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
44
  # Perform the NS iterations
45
  for a, b, c in [
46
  (4.0848, -6.8946, 2.9270),
 
49
  (2.8769, -3.1427, 1.2046),
50
  (2.8366, -3.0525, 1.2012),
51
  ]:
52
+ matmul_transpose_assign(X, buf1)
53
+ matmul_transpose_assign(buf1, buf2)
54
+ buf1.mul_(b).add_(buf2, alpha=c)
55
+ X = torch.addmm(X, buf1, X, alpha=1.0, beta=a)
 
 
 
56
 
57
  if G.size(0) > G.size(1):
58
  X = X.T
 
73
  qk_clip_state = None
74
 
75
 
76
+ def split_elems_for_src(param, state, src_rank, num_ranks) -> int:
77
+ rows = param.shape[0]
78
+ cols = int(param.numel() // rows)
79
+ base, rem = divmod(rows, num_ranks)
80
+ my_rows = base + (1 if src_rank < rem else 0)
81
+ return my_rows * cols
82
+
83
+
84
  @torch.no_grad()
85
+ def _alloc_gathered_grad(params, param_to_state, rank, compute_stream):
86
+ with torch.cuda.stream(compute_stream):
87
+ for p in params:
88
+ state = param_to_state[id(p)]
89
+ if rank == state.worker_rank:
90
+ num_ranks = dist.get_world_size(group=state.process_group)
91
+ state.gathered_grad = torch.empty(p.grad.numel(),
92
+ dtype=COMM_DTYPE,
93
+ device="cuda")
94
+ else:
95
+ state.gathered_grad = None
96
+
97
+ alloc_event = torch.cuda.Event()
98
+ alloc_event.record(compute_stream)
99
+ return alloc_event
100
+
101
+
102
+ @torch.no_grad()
103
+ def _all2all_gather(params, param_to_state, rank, comm_stream, none_grad,
104
+ alloc_event):
105
  with torch.cuda.stream(comm_stream):
106
+ process_group = param_to_state[id(params[0])].process_group
107
+ num_ranks = dist.get_world_size(group=process_group)
108
 
109
+ # Calculate sending tensors
110
+ per_dst = [[] for _ in range(num_ranks)]
111
+ send_counts = [0] * num_ranks
112
+ for p in params:
113
+ state = param_to_state[id(p)]
114
+ dst = state.worker_rank
115
+ assert dst < num_ranks
116
+ shard_elems = split_elems_for_src(p, state, rank, num_ranks)
117
+ g = p.grad
118
+ g = g.to_local().to(COMM_DTYPE).contiguous().view(-1)
119
+ assert g.numel() == shard_elems
120
+ per_dst[dst].append(g)
121
+ send_counts[dst] += shard_elems
122
+
123
+ assert all(
124
+ len(v) > 0
125
+ for v in per_dst), "all params should be sharded to all devices"
126
+
127
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
128
+ owner_params = [
129
+ p for p in params if param_to_state[id(p)].worker_rank == rank
130
+ ]
131
+
132
+ # Calculate receiving tensors
133
+ recv_counts = [0] * num_ranks
134
+ for src in range(num_ranks):
135
+ total = 0
136
+ for p in owner_params:
137
+ state = param_to_state[id(p)]
138
+ assert state.worker_rank == rank
139
+ total += split_elems_for_src(p, state, src, num_ranks)
140
+ recv_counts[src] = total
141
+
142
+ recv_total = sum(recv_counts)
143
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
144
+ dist.all_to_all_single(
145
+ recv_buf,
146
+ send_buf,
147
+ output_split_sizes=recv_counts,
148
+ input_split_sizes=send_counts,
149
+ group=process_group,
150
  )
151
+
152
+ # Reconstructs gathered grad from the received buffer
153
+ #
154
+ # recv_buf (num ranks = 3)
155
+ #
156
+ # From rank 0 From rank 1 From rank 2
157
+ # | p1_0, p2_0, p3_0 | p1_1, p2_1, p3_1 | p1_2, p2_2, p3_2 |
158
+ #
159
+ # Outer loop:
160
+ # rank 0 -> rank 1 -> rank2
161
+ #
162
+ # Inner loop:
163
+ # p1_n -> p2_n -> p3_n
164
+
165
+ comm_stream.wait_event(alloc_event)
166
+
167
+ off = 0
168
+ write_offsets = {id(p): 0 for p in owner_params}
169
+ for src in range(num_ranks):
170
+ if recv_counts[src] == 0:
171
+ continue
172
+
173
+ block = recv_counts[src]
174
+ inner_off = 0
175
+ for p in owner_params:
176
+ state = param_to_state[id(p)]
177
+ assert state.worker_rank == rank
178
+ n = split_elems_for_src(p, state, src, num_ranks)
179
+ assert n > 0
180
+
181
+ sg = recv_buf.narrow(0, off + inner_off, n)
182
+ woff = write_offsets[id(p)]
183
+ dst = state.gathered_grad.narrow(0, woff, n)
184
+ dst.copy_(sg)
185
+
186
+ write_offsets[id(p)] += n
187
+ inner_off += n
188
+ off += block
189
+
190
+ for p in params:
191
+ state = param_to_state[id(p)]
192
+ if state.worker_rank == rank:
193
+ state.gathered_grad = state.gathered_grad.view_as(p)
194
+ state.gather_event = torch.cuda.Event()
195
+ state.gather_event.record(comm_stream)
196
+ else:
197
+ state.gathered_grad = None
198
+ state.gather_event = None
199
+ if none_grad:
200
+ p.grad = None
201
 
202
 
203
  @torch.no_grad()
 
211
  raise RuntimeError("Gather event must be set before compute.")
212
  compute_stream.wait_event(state.gather_event)
213
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
214
+ state.gathered_grad = None
215
  state.computed_u = u
216
+ state.compute_event = torch.cuda.Event()
217
+ state.compute_event.record()
218
+ else:
219
+ state.computed_u = None
220
+ state.compute_event = None
221
 
222
 
223
  @torch.no_grad()
224
+ def _alloc_scattered_u(params, param_to_state, rank, compute_stream):
225
+ with torch.cuda.stream(compute_stream):
226
+ for p in params:
227
+ state = param_to_state[id(p)]
228
+ state.scattered_u = torch.empty_like(p.to_local(),
229
+ dtype=COMM_DTYPE)
230
+
231
+ alloc_event = torch.cuda.Event()
232
+ alloc_event.record(compute_stream)
233
+ return alloc_event
234
 
235
+
236
+ def _all2all_scatter(params, param_to_state, rank, comm_stream, alloc_event):
237
  with torch.cuda.stream(comm_stream):
238
+ process_group = param_to_state[id(params[0])].process_group
239
+ num_ranks = dist.get_world_size(group=process_group)
240
+ owner_params = [
241
+ p for p in params if param_to_state[id(p)].worker_rank == rank
242
+ ]
243
 
244
+ per_dst = [[] for _ in range(num_ranks)]
245
+ send_counts = [0] * num_ranks
 
 
246
 
247
+ if owner_params:
248
+ for p in owner_params:
249
+ state = param_to_state[id(p)]
250
+ if state.compute_event is None:
251
+ raise RuntimeError(
252
+ "Compute event must be set before scatter.")
253
+ comm_stream.wait_event(state.compute_event)
254
+ state.gathered_grad = None
255
 
256
+ assert state.computed_u is not None
257
+
258
+ u_full = state.computed_u.to(COMM_DTYPE).contiguous().view(-1)
259
+
260
+ offset = 0
261
+ for dst in range(num_ranks):
262
+ n = split_elems_for_src(p, state, dst, num_ranks)
263
+ assert n > 0
264
+
265
+ su = u_full.narrow(0, offset, n)
266
+ per_dst[dst].append(su)
267
+ send_counts[dst] += n
268
+ offset += n
269
+
270
+ assert offset == u_full.numel()
271
+
272
+ if any(len(v) > 0 for v in per_dst):
273
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
274
+ else:
275
+ # all_to_all requires participation from all ranks
276
+ # Even non-owner ranks must join the collective call
277
+ send_buf = torch.empty(0, dtype=COMM_DTYPE, device="cuda")
278
+
279
+ recv_counts = [0] * num_ranks
280
+ for src in range(num_ranks):
281
+ total = 0
282
+ for p in params:
283
+ state = param_to_state[id(p)]
284
+ if state.worker_rank != src:
285
+ continue
286
+ total += split_elems_for_src(p, state, rank, num_ranks)
287
+ recv_counts[src] = total
288
+
289
+ recv_total = sum(recv_counts)
290
+ assert recv_total > 0
291
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
292
+
293
+ dist.all_to_all_single(
294
+ recv_buf,
295
+ send_buf,
296
+ output_split_sizes=recv_counts,
297
+ input_split_sizes=send_counts,
298
+ group=process_group,
299
  )
300
+
301
+ comm_stream.wait_event(alloc_event)
302
+
303
+ off = 0
304
+ for src in range(num_ranks):
305
+ block = recv_counts[src]
306
+ if block == 0:
307
+ continue
308
+
309
+ inner_off = 0
310
+ for p in params:
311
+ state = param_to_state[id(p)]
312
+ if state.worker_rank != src:
313
+ continue
314
+ n = split_elems_for_src(p, state, rank, num_ranks)
315
+ assert n > 0
316
+
317
+ flat_local = recv_buf.narrow(0, off + inner_off,
318
+ n).view_as(p.to_local())
319
+ state.scattered_u.copy_(flat_local)
320
+
321
+ state.scatter_event = torch.cuda.Event()
322
+ state.scatter_event.record(comm_stream)
323
+ inner_off += n
324
+
325
+ assert inner_off == block
326
+ off += block
327
 
328
 
329
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
 
602
  if mesh is None:
603
  mesh = p.device_mesh
604
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
605
+ self.rank = dist.get_rank(group=process_group)
606
  elif mesh != p.device_mesh:
607
  raise ValueError("All parameters must be on the same mesh.")
608
 
609
+ num_ranks = dist.get_world_size(group=process_group)
610
  param_to_state[id(p)] = _muon_state()
611
+ param_to_state[id(
612
+ p)].worker_rank = shard_mesh[round_robin].item() % num_ranks
613
  param_to_state[id(p)].process_group = process_group
614
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
615
  param_to_state[id(p)].qk_clip_state = qk_clip_state
 
639
  else:
640
  g = buf
641
 
642
+ u = _zeropower_via_newtonschulz5(g.to(COMM_DTYPE),
643
  steps=group["ns_steps"])
644
 
645
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
 
654
  def _update_g(self, p, g, group, momentum):
655
  # calc update
656
  state = self.state[p]
657
+ buf = state.setdefault("momentum_buffer", torch.zeros_like(g))
658
+ torch.add(g, buf, alpha=momentum, out=buf)
 
 
659
  if group["nesterov"]:
660
+ g.add_(buf, alpha=momentum)
661
+ return g
662
+ return buf
 
663
 
664
  @staticmethod
665
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
 
743
  param_to_state, ordered_params = self.init_state_and_assign_params(
744
  names, params, group, qk_logits)
745
 
746
+ def enqueue_all2all_gather(start_idx, chunk_size):
747
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
748
+ if target_params:
749
+ alloc_event = _alloc_gathered_grad(target_params,
750
+ param_to_state, self.rank,
751
+ self.compute_stream)
752
+ _all2all_gather(target_params, param_to_state, self.rank,
753
+ self.comm_stream, group["none_grad"],
754
+ alloc_event)
755
 
756
  def enqueue_computes(start_idx, chunk_size):
757
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
759
  _compute_u(p, state, group["ns_steps"], self.rank,
760
  self.compute_stream)
761
 
762
+ def enqueue_all2all_scatter(start_idx, chunk_size):
763
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
764
+ if target_params:
765
+ alloc_event = _alloc_scattered_u(target_params, param_to_state,
766
+ self.rank,
767
+ self.compute_stream)
768
+ _all2all_scatter(target_params, param_to_state, self.rank,
769
+ self.comm_stream, alloc_event)
770
 
771
  def enqueue_update_param(start_idx, chunk_size):
772
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
781
  # Wait grad update
782
  self.comm_stream.wait_stream(torch.cuda.current_stream())
783
 
784
+ PRE_STEP = 5
785
+ for i in range(0, PRE_STEP):
786
+ enqueue_all2all_gather(i * chunk_size, chunk_size)
787
+ enqueue_computes(i * chunk_size, chunk_size)
788
+
789
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
790
+ enqueue_all2all_scatter(i, chunk_size)
791
+ enqueue_all2all_gather(i + PRE_STEP * chunk_size, chunk_size)
792
+ enqueue_update_param(i, chunk_size)
793
+ enqueue_computes(i + PRE_STEP * chunk_size, chunk_size)
 
 
794
 
795
  # Wait the last update_param to finish
796
  torch.cuda.current_stream().wait_stream(self.compute_stream)
build/torch28-cxx11-cu128-x86_64-linux/optimizer/_ops.py CHANGED
@@ -1,9 +1,9 @@
1
  import torch
2
- from . import _optimizer_9c21645_dirty
3
- ops = torch.ops._optimizer_9c21645_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
- return f"_optimizer_9c21645_dirty::{op_name}"
 
1
  import torch
2
+ from . import _optimizer_fceb334_dirty
3
+ ops = torch.ops._optimizer_fceb334_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
+ return f"_optimizer_fceb334_dirty::{op_name}"
build/{torch28-cxx11-cu129-x86_64-linux/optimizer/_optimizer_9c21645_dirty.abi3.so → torch28-cxx11-cu128-x86_64-linux/optimizer/_optimizer_fceb334_dirty.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:eb40a06623bb3668b82ff248b5a3c1bcf41e7f3f860888b261505b3a71257bc7
3
  size 1883344
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5b7313ff1257a692e4e5cfdec474b10a7a795f2e87edb0099c7e5d21f588634f
3
  size 1883344
build/torch28-cxx11-cu128-x86_64-linux/optimizer/matmul_transpose_triton.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import triton
3
+ import triton.language as tl
4
+
5
+
6
+ def get_autotune_config():
7
+ return [
8
+ triton.Config(
9
+ {
10
+ 'BLOCK_SIZE_M': blk_m,
11
+ 'BLOCK_SIZE_K': blk_k,
12
+ 'GROUP_SIZE_M': grp_sz
13
+ },
14
+ num_stages=n_stages,
15
+ num_warps=n_warps) for blk_m in [32, 64, 128]
16
+ for blk_k in [32, 64] for grp_sz in [8] for n_stages in [3, 4, 5]
17
+ for n_warps in [4, 8]
18
+ ]
19
+
20
+
21
+ @triton.autotune(
22
+ configs=get_autotune_config(),
23
+ key=['M', 'K'],
24
+ )
25
+ @triton.jit
26
+ def mmt_kernel(x, y, M, K, stride_xm, stride_xk, stride_ym, stride_yn,
27
+ BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_K: tl.constexpr,
28
+ GROUP_SIZE_M: tl.constexpr):
29
+ """
30
+ Core kernel jit function of matmul_transpose that computes y = x @ x.T
31
+ The code is a simple adaptation from the triton `matmul` tutorial:
32
+ https://triton-lang.org/main/getting-started/tutorials/03-matrix-multiplication.html
33
+ """
34
+ pid = tl.program_id(axis=0)
35
+ num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
36
+ num_pid_n = tl.cdiv(M, BLOCK_SIZE_M)
37
+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
38
+ group_id = pid // num_pid_in_group
39
+ first_pid_m = group_id * GROUP_SIZE_M
40
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
41
+ pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
42
+ pid_n = (pid % num_pid_in_group) // group_size_m
43
+ if pid_m > pid_n:
44
+ return
45
+
46
+ offs_xm = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
47
+ offs_xn = (pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
48
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
49
+ # we use a & b ptrs to denote different rows of x.
50
+ a_ptrs = x + (offs_xm[:, None] * stride_xm + offs_k[None, :] * stride_xk)
51
+ b_ptrs = x + (offs_xn[:, None] * stride_xm + offs_k[None, :] * stride_xk)
52
+
53
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_M), dtype=tl.float32)
54
+
55
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
56
+ a = tl.load(a_ptrs,
57
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
58
+ other=0.0)
59
+ b = tl.load(b_ptrs,
60
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
61
+ other=0.0)
62
+ accumulator = tl.dot(a, tl.permute(b, (1, 0)), accumulator)
63
+ a_ptrs += BLOCK_SIZE_K * stride_xk
64
+ b_ptrs += BLOCK_SIZE_K * stride_xk
65
+ # use dtype.element_ty to accommodate different input datatypes as in cpp templates
66
+ # https://github.com/triton-lang/triton/issues/2252
67
+ c = accumulator.to(x.dtype.element_ty)
68
+
69
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
70
+ offs_cn = pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
71
+ c_ptrs = y + stride_ym * offs_cm[:, None] + stride_yn * offs_cn[None, :]
72
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < M)
73
+ tl.store(c_ptrs, c, mask=c_mask)
74
+
75
+ # transpose and copy
76
+ if pid_m < pid_n:
77
+ ct_ptrs = y + stride_ym * offs_cn[:,
78
+ None] + stride_yn * offs_cm[None, :]
79
+ ct_mask = (offs_cn[:, None] < M) & (offs_cm[None, :] < M)
80
+ tl.store(ct_ptrs, tl.permute(c, (1, 0)), mask=ct_mask)
81
+
82
+
83
+ def matmul_transpose_assign(d_in, d_out):
84
+ assert d_in.is_cuda, "Input `d_in` must be a CUDA tensor"
85
+ assert d_out.is_cuda, "Input `d_out` must be a CUDA tensor"
86
+ assert d_in.device == d_out.device, "Inputs `d_in` and `d_out` must be on the same CUDA device"
87
+ assert d_in.dtype == d_out.dtype, "Inputs must have the same data type"
88
+ assert d_in.ndim == 2, "Input `d_in` must be a 2D tensor"
89
+ assert d_out.ndim == 2, "Input `d_out` must be a 2D tensor"
90
+ assert d_in.size(0) == d_out.size(0) == d_out.size(0), \
91
+ "First dimension of `d_in` must match first and second dimension of `d_out`"
92
+
93
+ d_in = d_in.contiguous()
94
+ M, K = d_in.shape
95
+ grid = lambda META: (triton.cdiv(M, META['BLOCK_SIZE_M']) * triton.cdiv(
96
+ M, META['BLOCK_SIZE_M']), )
97
+ with torch.cuda.device(d_in.device.index):
98
+ mmt_kernel[grid](d_in, d_out, M, K, d_in.stride(0), d_in.stride(1),
99
+ d_out.stride(0), d_out.stride(1))
100
+
101
+
102
+ def matmul_transpose(d_in):
103
+ M, _ = d_in.shape
104
+ d_out = torch.empty((M, M), device=d_in.device, dtype=d_in.dtype)
105
+ matmul_transpose_assign(d_in, d_out)
106
+ return d_out
build/torch28-cxx11-cu128-x86_64-linux/optimizer/muon.py CHANGED
@@ -8,14 +8,19 @@ import torch
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
 
 
11
  logger = logging.getLogger(__name__)
12
 
 
 
13
 
14
  # This code snippet is a modified version adapted from the following GitHub repositories:
15
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
16
  # Muon's Newton–Schulz iteration causes high variance in singular values
17
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
18
  @torch.no_grad()
 
19
  def _zeropower_via_newtonschulz5(G, steps):
20
  """
21
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
@@ -27,13 +32,15 @@ def _zeropower_via_newtonschulz5(G, steps):
27
  performance at all relative to UV^T, where USV^T = G is the SVD.
28
  """
29
  assert len(G.shape) == 2
30
- assert G.dtype == torch.bfloat16
31
  X = G # no manual typecast
32
 
33
  if G.size(0) > G.size(1):
34
  X = X.T
35
  # Ensure spectral norm is at most 1
36
  X = X / (X.norm() + 1e-7)
 
 
37
  # Perform the NS iterations
38
  for a, b, c in [
39
  (4.0848, -6.8946, 2.9270),
@@ -42,13 +49,10 @@ def _zeropower_via_newtonschulz5(G, steps):
42
  (2.8769, -3.1427, 1.2046),
43
  (2.8366, -3.0525, 1.2012),
44
  ]:
45
- A = X @ X.T
46
- # B = (
47
- # b * A + c * A @ A
48
- # )
49
- B = torch.addmm(A, A, A, alpha=c, beta=b)
50
- # X = a * X + B @ X
51
- X = torch.addmm(X, B, X, alpha=1.0, beta=a)
52
 
53
  if G.size(0) > G.size(1):
54
  X = X.T
@@ -69,51 +73,131 @@ class _muon_state:
69
  qk_clip_state = None
70
 
71
 
 
 
 
 
 
 
 
 
72
  @torch.no_grad()
73
- def _gather(p, state, rank, comm_stream, none_grad):
74
- """
75
- Gather the gradients to worker_rank.
76
- If none_grad is True, free p.grad after the gather.
77
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  with torch.cuda.stream(comm_stream):
79
- g = p.grad
 
80
 
81
- if rank == state.worker_rank:
82
- num_ranks = dist.get_world_size(group=state.process_group)
83
- gather_list = [
84
- torch.empty_like(g.to_local(), dtype=torch.bfloat16)
85
- for _ in range(num_ranks)
86
- ]
87
- else:
88
- gather_list = None
89
-
90
- g = g.to(torch.bfloat16)
91
- torch.distributed.gather(
92
- g.to_local(),
93
- dst=state.worker_rank,
94
- gather_list=gather_list,
95
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  )
97
- if rank == state.worker_rank:
98
- if state.gathered_grad is not None:
99
- raise RuntimeError(
100
- "Gather event already exists, which should not happen.")
101
- state.gathered_grad = torch.cat(gather_list, dim=0)
102
- state.gather_event = torch.cuda.Event()
103
- state.gather_event.record()
104
- else:
105
- state.gathered_grad = None
106
- state.gather_event = None
107
- gather_list = None
108
- if none_grad:
109
- # We can safely free p.grad without calling record_stream:
110
- # p.grad.to_local().record_stream(comm_stream)
111
- # Explanation:
112
- # 1. p.grad is created on the default stream, but the default stream
113
- # is synchronized with the comm stream later.
114
- # 2. There is no further activity on the default stream before the optimizer finishes.
115
- # Therefore, it is safe to free p.grad directly on the comm stream.
116
- p.grad = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
 
119
  @torch.no_grad()
@@ -127,45 +211,119 @@ def _compute_u(p, state, steps, rank, compute_stream):
127
  raise RuntimeError("Gather event must be set before compute.")
128
  compute_stream.wait_event(state.gather_event)
129
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
 
130
  state.computed_u = u
131
- state.scattered_u = torch.empty_like(p.to_local(),
132
- dtype=torch.bfloat16)
133
- state.compute_event = torch.cuda.Event()
134
- state.compute_event.record()
135
- u = None
136
 
137
 
138
  @torch.no_grad()
139
- def _scatter(p, state, rank, comm_stream):
140
- """
141
- Scatter the computed_u from worker_rank to all ranks.
142
- """
 
 
 
 
 
 
143
 
 
 
144
  with torch.cuda.stream(comm_stream):
145
- if state.compute_event is None:
146
- raise RuntimeError("Compute event must be set before scatter.")
147
- comm_stream.wait_event(state.compute_event)
 
 
148
 
149
- if rank == state.worker_rank:
150
- num_ranks = dist.get_world_size(group=state.process_group)
151
- # Clear the gathered gradient to free memory
152
- state.gathered_grad = None
153
 
154
- u = state.computed_u
155
- scatter_list = list(torch.split(u, p.size(0) // num_ranks, dim=0))
156
- scatter_list = [s.contiguous() for s in scatter_list]
157
- else:
158
- scatter_list = None
 
 
 
159
 
160
- torch.distributed.scatter(
161
- state.scattered_u,
162
- scatter_list=scatter_list,
163
- src=state.worker_rank,
164
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  )
166
- state.scatter_event = torch.cuda.Event()
167
- state.scatter_event.record()
168
- scatter_list = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
 
171
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
@@ -444,11 +602,14 @@ class Muon(torch.optim.Optimizer):
444
  if mesh is None:
445
  mesh = p.device_mesh
446
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
 
447
  elif mesh != p.device_mesh:
448
  raise ValueError("All parameters must be on the same mesh.")
449
 
 
450
  param_to_state[id(p)] = _muon_state()
451
- param_to_state[id(p)].worker_rank = shard_mesh[round_robin].item()
 
452
  param_to_state[id(p)].process_group = process_group
453
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
454
  param_to_state[id(p)].qk_clip_state = qk_clip_state
@@ -478,7 +639,7 @@ class Muon(torch.optim.Optimizer):
478
  else:
479
  g = buf
480
 
481
- u = _zeropower_via_newtonschulz5(g.bfloat16(),
482
  steps=group["ns_steps"])
483
 
484
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
@@ -493,15 +654,12 @@ class Muon(torch.optim.Optimizer):
493
  def _update_g(self, p, g, group, momentum):
494
  # calc update
495
  state = self.state[p]
496
- if "momentum_buffer" not in state:
497
- state["momentum_buffer"] = torch.zeros_like(g)
498
- buf = state["momentum_buffer"]
499
- buf.mul_(momentum).add_(g)
500
  if group["nesterov"]:
501
- g = g.add(buf, alpha=momentum)
502
- else:
503
- g = buf
504
- return g
505
 
506
  @staticmethod
507
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
@@ -585,11 +743,15 @@ class Muon(torch.optim.Optimizer):
585
  param_to_state, ordered_params = self.init_state_and_assign_params(
586
  names, params, group, qk_logits)
587
 
588
- def enqueue_gathers(start_idx, chunk_size):
589
- for p in ordered_params[start_idx:start_idx + chunk_size]:
590
- state = param_to_state[id(p)]
591
- _gather(p, state, self.rank, self.comm_stream,
592
- group["none_grad"])
 
 
 
 
593
 
594
  def enqueue_computes(start_idx, chunk_size):
595
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -597,10 +759,14 @@ class Muon(torch.optim.Optimizer):
597
  _compute_u(p, state, group["ns_steps"], self.rank,
598
  self.compute_stream)
599
 
600
- def enqueue_scatters(start_idx, chunk_size):
601
- for p in ordered_params[start_idx:start_idx + chunk_size]:
602
- state = param_to_state[id(p)]
603
- _scatter(p, state, self.rank, self.comm_stream)
 
 
 
 
604
 
605
  def enqueue_update_param(start_idx, chunk_size):
606
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -615,14 +781,16 @@ class Muon(torch.optim.Optimizer):
615
  # Wait grad update
616
  self.comm_stream.wait_stream(torch.cuda.current_stream())
617
 
618
- enqueue_gathers(0, chunk_size)
 
 
 
 
619
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
620
- enqueue_computes(i, chunk_size)
621
- if i > 0:
622
- enqueue_update_param(i - chunk_size, chunk_size)
623
- enqueue_gathers(i + chunk_size, chunk_size)
624
- enqueue_scatters(i, chunk_size)
625
- enqueue_update_param(i, chunk_size)
626
 
627
  # Wait the last update_param to finish
628
  torch.cuda.current_stream().wait_stream(self.compute_stream)
 
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
11
+ from .matmul_transpose_triton import matmul_transpose_assign
12
+
13
  logger = logging.getLogger(__name__)
14
 
15
+ COMM_DTYPE = torch.bfloat16
16
+
17
 
18
  # This code snippet is a modified version adapted from the following GitHub repositories:
19
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
20
  # Muon's Newton–Schulz iteration causes high variance in singular values
21
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
22
  @torch.no_grad()
23
+ # matmul_transpose_assign from : https://github.com/nil0x9/flash-muon
24
  def _zeropower_via_newtonschulz5(G, steps):
25
  """
26
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
 
32
  performance at all relative to UV^T, where USV^T = G is the SVD.
33
  """
34
  assert len(G.shape) == 2
35
+ assert G.dtype == COMM_DTYPE
36
  X = G # no manual typecast
37
 
38
  if G.size(0) > G.size(1):
39
  X = X.T
40
  # Ensure spectral norm is at most 1
41
  X = X / (X.norm() + 1e-7)
42
+ buf1 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
43
+ buf2 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
44
  # Perform the NS iterations
45
  for a, b, c in [
46
  (4.0848, -6.8946, 2.9270),
 
49
  (2.8769, -3.1427, 1.2046),
50
  (2.8366, -3.0525, 1.2012),
51
  ]:
52
+ matmul_transpose_assign(X, buf1)
53
+ matmul_transpose_assign(buf1, buf2)
54
+ buf1.mul_(b).add_(buf2, alpha=c)
55
+ X = torch.addmm(X, buf1, X, alpha=1.0, beta=a)
 
 
 
56
 
57
  if G.size(0) > G.size(1):
58
  X = X.T
 
73
  qk_clip_state = None
74
 
75
 
76
+ def split_elems_for_src(param, state, src_rank, num_ranks) -> int:
77
+ rows = param.shape[0]
78
+ cols = int(param.numel() // rows)
79
+ base, rem = divmod(rows, num_ranks)
80
+ my_rows = base + (1 if src_rank < rem else 0)
81
+ return my_rows * cols
82
+
83
+
84
  @torch.no_grad()
85
+ def _alloc_gathered_grad(params, param_to_state, rank, compute_stream):
86
+ with torch.cuda.stream(compute_stream):
87
+ for p in params:
88
+ state = param_to_state[id(p)]
89
+ if rank == state.worker_rank:
90
+ num_ranks = dist.get_world_size(group=state.process_group)
91
+ state.gathered_grad = torch.empty(p.grad.numel(),
92
+ dtype=COMM_DTYPE,
93
+ device="cuda")
94
+ else:
95
+ state.gathered_grad = None
96
+
97
+ alloc_event = torch.cuda.Event()
98
+ alloc_event.record(compute_stream)
99
+ return alloc_event
100
+
101
+
102
+ @torch.no_grad()
103
+ def _all2all_gather(params, param_to_state, rank, comm_stream, none_grad,
104
+ alloc_event):
105
  with torch.cuda.stream(comm_stream):
106
+ process_group = param_to_state[id(params[0])].process_group
107
+ num_ranks = dist.get_world_size(group=process_group)
108
 
109
+ # Calculate sending tensors
110
+ per_dst = [[] for _ in range(num_ranks)]
111
+ send_counts = [0] * num_ranks
112
+ for p in params:
113
+ state = param_to_state[id(p)]
114
+ dst = state.worker_rank
115
+ assert dst < num_ranks
116
+ shard_elems = split_elems_for_src(p, state, rank, num_ranks)
117
+ g = p.grad
118
+ g = g.to_local().to(COMM_DTYPE).contiguous().view(-1)
119
+ assert g.numel() == shard_elems
120
+ per_dst[dst].append(g)
121
+ send_counts[dst] += shard_elems
122
+
123
+ assert all(
124
+ len(v) > 0
125
+ for v in per_dst), "all params should be sharded to all devices"
126
+
127
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
128
+ owner_params = [
129
+ p for p in params if param_to_state[id(p)].worker_rank == rank
130
+ ]
131
+
132
+ # Calculate receiving tensors
133
+ recv_counts = [0] * num_ranks
134
+ for src in range(num_ranks):
135
+ total = 0
136
+ for p in owner_params:
137
+ state = param_to_state[id(p)]
138
+ assert state.worker_rank == rank
139
+ total += split_elems_for_src(p, state, src, num_ranks)
140
+ recv_counts[src] = total
141
+
142
+ recv_total = sum(recv_counts)
143
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
144
+ dist.all_to_all_single(
145
+ recv_buf,
146
+ send_buf,
147
+ output_split_sizes=recv_counts,
148
+ input_split_sizes=send_counts,
149
+ group=process_group,
150
  )
151
+
152
+ # Reconstructs gathered grad from the received buffer
153
+ #
154
+ # recv_buf (num ranks = 3)
155
+ #
156
+ # From rank 0 From rank 1 From rank 2
157
+ # | p1_0, p2_0, p3_0 | p1_1, p2_1, p3_1 | p1_2, p2_2, p3_2 |
158
+ #
159
+ # Outer loop:
160
+ # rank 0 -> rank 1 -> rank2
161
+ #
162
+ # Inner loop:
163
+ # p1_n -> p2_n -> p3_n
164
+
165
+ comm_stream.wait_event(alloc_event)
166
+
167
+ off = 0
168
+ write_offsets = {id(p): 0 for p in owner_params}
169
+ for src in range(num_ranks):
170
+ if recv_counts[src] == 0:
171
+ continue
172
+
173
+ block = recv_counts[src]
174
+ inner_off = 0
175
+ for p in owner_params:
176
+ state = param_to_state[id(p)]
177
+ assert state.worker_rank == rank
178
+ n = split_elems_for_src(p, state, src, num_ranks)
179
+ assert n > 0
180
+
181
+ sg = recv_buf.narrow(0, off + inner_off, n)
182
+ woff = write_offsets[id(p)]
183
+ dst = state.gathered_grad.narrow(0, woff, n)
184
+ dst.copy_(sg)
185
+
186
+ write_offsets[id(p)] += n
187
+ inner_off += n
188
+ off += block
189
+
190
+ for p in params:
191
+ state = param_to_state[id(p)]
192
+ if state.worker_rank == rank:
193
+ state.gathered_grad = state.gathered_grad.view_as(p)
194
+ state.gather_event = torch.cuda.Event()
195
+ state.gather_event.record(comm_stream)
196
+ else:
197
+ state.gathered_grad = None
198
+ state.gather_event = None
199
+ if none_grad:
200
+ p.grad = None
201
 
202
 
203
  @torch.no_grad()
 
211
  raise RuntimeError("Gather event must be set before compute.")
212
  compute_stream.wait_event(state.gather_event)
213
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
214
+ state.gathered_grad = None
215
  state.computed_u = u
216
+ state.compute_event = torch.cuda.Event()
217
+ state.compute_event.record()
218
+ else:
219
+ state.computed_u = None
220
+ state.compute_event = None
221
 
222
 
223
  @torch.no_grad()
224
+ def _alloc_scattered_u(params, param_to_state, rank, compute_stream):
225
+ with torch.cuda.stream(compute_stream):
226
+ for p in params:
227
+ state = param_to_state[id(p)]
228
+ state.scattered_u = torch.empty_like(p.to_local(),
229
+ dtype=COMM_DTYPE)
230
+
231
+ alloc_event = torch.cuda.Event()
232
+ alloc_event.record(compute_stream)
233
+ return alloc_event
234
 
235
+
236
+ def _all2all_scatter(params, param_to_state, rank, comm_stream, alloc_event):
237
  with torch.cuda.stream(comm_stream):
238
+ process_group = param_to_state[id(params[0])].process_group
239
+ num_ranks = dist.get_world_size(group=process_group)
240
+ owner_params = [
241
+ p for p in params if param_to_state[id(p)].worker_rank == rank
242
+ ]
243
 
244
+ per_dst = [[] for _ in range(num_ranks)]
245
+ send_counts = [0] * num_ranks
 
 
246
 
247
+ if owner_params:
248
+ for p in owner_params:
249
+ state = param_to_state[id(p)]
250
+ if state.compute_event is None:
251
+ raise RuntimeError(
252
+ "Compute event must be set before scatter.")
253
+ comm_stream.wait_event(state.compute_event)
254
+ state.gathered_grad = None
255
 
256
+ assert state.computed_u is not None
257
+
258
+ u_full = state.computed_u.to(COMM_DTYPE).contiguous().view(-1)
259
+
260
+ offset = 0
261
+ for dst in range(num_ranks):
262
+ n = split_elems_for_src(p, state, dst, num_ranks)
263
+ assert n > 0
264
+
265
+ su = u_full.narrow(0, offset, n)
266
+ per_dst[dst].append(su)
267
+ send_counts[dst] += n
268
+ offset += n
269
+
270
+ assert offset == u_full.numel()
271
+
272
+ if any(len(v) > 0 for v in per_dst):
273
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
274
+ else:
275
+ # all_to_all requires participation from all ranks
276
+ # Even non-owner ranks must join the collective call
277
+ send_buf = torch.empty(0, dtype=COMM_DTYPE, device="cuda")
278
+
279
+ recv_counts = [0] * num_ranks
280
+ for src in range(num_ranks):
281
+ total = 0
282
+ for p in params:
283
+ state = param_to_state[id(p)]
284
+ if state.worker_rank != src:
285
+ continue
286
+ total += split_elems_for_src(p, state, rank, num_ranks)
287
+ recv_counts[src] = total
288
+
289
+ recv_total = sum(recv_counts)
290
+ assert recv_total > 0
291
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
292
+
293
+ dist.all_to_all_single(
294
+ recv_buf,
295
+ send_buf,
296
+ output_split_sizes=recv_counts,
297
+ input_split_sizes=send_counts,
298
+ group=process_group,
299
  )
300
+
301
+ comm_stream.wait_event(alloc_event)
302
+
303
+ off = 0
304
+ for src in range(num_ranks):
305
+ block = recv_counts[src]
306
+ if block == 0:
307
+ continue
308
+
309
+ inner_off = 0
310
+ for p in params:
311
+ state = param_to_state[id(p)]
312
+ if state.worker_rank != src:
313
+ continue
314
+ n = split_elems_for_src(p, state, rank, num_ranks)
315
+ assert n > 0
316
+
317
+ flat_local = recv_buf.narrow(0, off + inner_off,
318
+ n).view_as(p.to_local())
319
+ state.scattered_u.copy_(flat_local)
320
+
321
+ state.scatter_event = torch.cuda.Event()
322
+ state.scatter_event.record(comm_stream)
323
+ inner_off += n
324
+
325
+ assert inner_off == block
326
+ off += block
327
 
328
 
329
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
 
602
  if mesh is None:
603
  mesh = p.device_mesh
604
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
605
+ self.rank = dist.get_rank(group=process_group)
606
  elif mesh != p.device_mesh:
607
  raise ValueError("All parameters must be on the same mesh.")
608
 
609
+ num_ranks = dist.get_world_size(group=process_group)
610
  param_to_state[id(p)] = _muon_state()
611
+ param_to_state[id(
612
+ p)].worker_rank = shard_mesh[round_robin].item() % num_ranks
613
  param_to_state[id(p)].process_group = process_group
614
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
615
  param_to_state[id(p)].qk_clip_state = qk_clip_state
 
639
  else:
640
  g = buf
641
 
642
+ u = _zeropower_via_newtonschulz5(g.to(COMM_DTYPE),
643
  steps=group["ns_steps"])
644
 
645
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
 
654
  def _update_g(self, p, g, group, momentum):
655
  # calc update
656
  state = self.state[p]
657
+ buf = state.setdefault("momentum_buffer", torch.zeros_like(g))
658
+ torch.add(g, buf, alpha=momentum, out=buf)
 
 
659
  if group["nesterov"]:
660
+ g.add_(buf, alpha=momentum)
661
+ return g
662
+ return buf
 
663
 
664
  @staticmethod
665
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
 
743
  param_to_state, ordered_params = self.init_state_and_assign_params(
744
  names, params, group, qk_logits)
745
 
746
+ def enqueue_all2all_gather(start_idx, chunk_size):
747
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
748
+ if target_params:
749
+ alloc_event = _alloc_gathered_grad(target_params,
750
+ param_to_state, self.rank,
751
+ self.compute_stream)
752
+ _all2all_gather(target_params, param_to_state, self.rank,
753
+ self.comm_stream, group["none_grad"],
754
+ alloc_event)
755
 
756
  def enqueue_computes(start_idx, chunk_size):
757
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
759
  _compute_u(p, state, group["ns_steps"], self.rank,
760
  self.compute_stream)
761
 
762
+ def enqueue_all2all_scatter(start_idx, chunk_size):
763
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
764
+ if target_params:
765
+ alloc_event = _alloc_scattered_u(target_params, param_to_state,
766
+ self.rank,
767
+ self.compute_stream)
768
+ _all2all_scatter(target_params, param_to_state, self.rank,
769
+ self.comm_stream, alloc_event)
770
 
771
  def enqueue_update_param(start_idx, chunk_size):
772
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
781
  # Wait grad update
782
  self.comm_stream.wait_stream(torch.cuda.current_stream())
783
 
784
+ PRE_STEP = 5
785
+ for i in range(0, PRE_STEP):
786
+ enqueue_all2all_gather(i * chunk_size, chunk_size)
787
+ enqueue_computes(i * chunk_size, chunk_size)
788
+
789
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
790
+ enqueue_all2all_scatter(i, chunk_size)
791
+ enqueue_all2all_gather(i + PRE_STEP * chunk_size, chunk_size)
792
+ enqueue_update_param(i, chunk_size)
793
+ enqueue_computes(i + PRE_STEP * chunk_size, chunk_size)
 
 
794
 
795
  # Wait the last update_param to finish
796
  torch.cuda.current_stream().wait_stream(self.compute_stream)
build/torch28-cxx11-cu129-x86_64-linux/optimizer/_ops.py CHANGED
@@ -1,9 +1,9 @@
1
  import torch
2
- from . import _optimizer_9c21645_dirty
3
- ops = torch.ops._optimizer_9c21645_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
- return f"_optimizer_9c21645_dirty::{op_name}"
 
1
  import torch
2
+ from . import _optimizer_fceb334_dirty
3
+ ops = torch.ops._optimizer_fceb334_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
+ return f"_optimizer_fceb334_dirty::{op_name}"
build/{torch27-cxx11-cu128-x86_64-linux/optimizer/_optimizer_9c21645_dirty.abi3.so → torch28-cxx11-cu129-x86_64-linux/optimizer/_optimizer_fceb334_dirty.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:dae71b7e998e72130093a86f8c983c3379510e23525e3cdcd4afe5c21bf4d3db
3
  size 1883344
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:afde41795cbfff495808e4a5da7c9309795bf9b1e49e08f3cb459b5115c9bf98
3
  size 1883344
build/torch28-cxx11-cu129-x86_64-linux/optimizer/matmul_transpose_triton.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import triton
3
+ import triton.language as tl
4
+
5
+
6
+ def get_autotune_config():
7
+ return [
8
+ triton.Config(
9
+ {
10
+ 'BLOCK_SIZE_M': blk_m,
11
+ 'BLOCK_SIZE_K': blk_k,
12
+ 'GROUP_SIZE_M': grp_sz
13
+ },
14
+ num_stages=n_stages,
15
+ num_warps=n_warps) for blk_m in [32, 64, 128]
16
+ for blk_k in [32, 64] for grp_sz in [8] for n_stages in [3, 4, 5]
17
+ for n_warps in [4, 8]
18
+ ]
19
+
20
+
21
+ @triton.autotune(
22
+ configs=get_autotune_config(),
23
+ key=['M', 'K'],
24
+ )
25
+ @triton.jit
26
+ def mmt_kernel(x, y, M, K, stride_xm, stride_xk, stride_ym, stride_yn,
27
+ BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_K: tl.constexpr,
28
+ GROUP_SIZE_M: tl.constexpr):
29
+ """
30
+ Core kernel jit function of matmul_transpose that computes y = x @ x.T
31
+ The code is a simple adaptation from the triton `matmul` tutorial:
32
+ https://triton-lang.org/main/getting-started/tutorials/03-matrix-multiplication.html
33
+ """
34
+ pid = tl.program_id(axis=0)
35
+ num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
36
+ num_pid_n = tl.cdiv(M, BLOCK_SIZE_M)
37
+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
38
+ group_id = pid // num_pid_in_group
39
+ first_pid_m = group_id * GROUP_SIZE_M
40
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
41
+ pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
42
+ pid_n = (pid % num_pid_in_group) // group_size_m
43
+ if pid_m > pid_n:
44
+ return
45
+
46
+ offs_xm = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
47
+ offs_xn = (pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
48
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
49
+ # we use a & b ptrs to denote different rows of x.
50
+ a_ptrs = x + (offs_xm[:, None] * stride_xm + offs_k[None, :] * stride_xk)
51
+ b_ptrs = x + (offs_xn[:, None] * stride_xm + offs_k[None, :] * stride_xk)
52
+
53
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_M), dtype=tl.float32)
54
+
55
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
56
+ a = tl.load(a_ptrs,
57
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
58
+ other=0.0)
59
+ b = tl.load(b_ptrs,
60
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
61
+ other=0.0)
62
+ accumulator = tl.dot(a, tl.permute(b, (1, 0)), accumulator)
63
+ a_ptrs += BLOCK_SIZE_K * stride_xk
64
+ b_ptrs += BLOCK_SIZE_K * stride_xk
65
+ # use dtype.element_ty to accommodate different input datatypes as in cpp templates
66
+ # https://github.com/triton-lang/triton/issues/2252
67
+ c = accumulator.to(x.dtype.element_ty)
68
+
69
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
70
+ offs_cn = pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
71
+ c_ptrs = y + stride_ym * offs_cm[:, None] + stride_yn * offs_cn[None, :]
72
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < M)
73
+ tl.store(c_ptrs, c, mask=c_mask)
74
+
75
+ # transpose and copy
76
+ if pid_m < pid_n:
77
+ ct_ptrs = y + stride_ym * offs_cn[:,
78
+ None] + stride_yn * offs_cm[None, :]
79
+ ct_mask = (offs_cn[:, None] < M) & (offs_cm[None, :] < M)
80
+ tl.store(ct_ptrs, tl.permute(c, (1, 0)), mask=ct_mask)
81
+
82
+
83
+ def matmul_transpose_assign(d_in, d_out):
84
+ assert d_in.is_cuda, "Input `d_in` must be a CUDA tensor"
85
+ assert d_out.is_cuda, "Input `d_out` must be a CUDA tensor"
86
+ assert d_in.device == d_out.device, "Inputs `d_in` and `d_out` must be on the same CUDA device"
87
+ assert d_in.dtype == d_out.dtype, "Inputs must have the same data type"
88
+ assert d_in.ndim == 2, "Input `d_in` must be a 2D tensor"
89
+ assert d_out.ndim == 2, "Input `d_out` must be a 2D tensor"
90
+ assert d_in.size(0) == d_out.size(0) == d_out.size(0), \
91
+ "First dimension of `d_in` must match first and second dimension of `d_out`"
92
+
93
+ d_in = d_in.contiguous()
94
+ M, K = d_in.shape
95
+ grid = lambda META: (triton.cdiv(M, META['BLOCK_SIZE_M']) * triton.cdiv(
96
+ M, META['BLOCK_SIZE_M']), )
97
+ with torch.cuda.device(d_in.device.index):
98
+ mmt_kernel[grid](d_in, d_out, M, K, d_in.stride(0), d_in.stride(1),
99
+ d_out.stride(0), d_out.stride(1))
100
+
101
+
102
+ def matmul_transpose(d_in):
103
+ M, _ = d_in.shape
104
+ d_out = torch.empty((M, M), device=d_in.device, dtype=d_in.dtype)
105
+ matmul_transpose_assign(d_in, d_out)
106
+ return d_out
build/torch28-cxx11-cu129-x86_64-linux/optimizer/muon.py CHANGED
@@ -8,14 +8,19 @@ import torch
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
 
 
11
  logger = logging.getLogger(__name__)
12
 
 
 
13
 
14
  # This code snippet is a modified version adapted from the following GitHub repositories:
15
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
16
  # Muon's Newton–Schulz iteration causes high variance in singular values
17
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
18
  @torch.no_grad()
 
19
  def _zeropower_via_newtonschulz5(G, steps):
20
  """
21
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
@@ -27,13 +32,15 @@ def _zeropower_via_newtonschulz5(G, steps):
27
  performance at all relative to UV^T, where USV^T = G is the SVD.
28
  """
29
  assert len(G.shape) == 2
30
- assert G.dtype == torch.bfloat16
31
  X = G # no manual typecast
32
 
33
  if G.size(0) > G.size(1):
34
  X = X.T
35
  # Ensure spectral norm is at most 1
36
  X = X / (X.norm() + 1e-7)
 
 
37
  # Perform the NS iterations
38
  for a, b, c in [
39
  (4.0848, -6.8946, 2.9270),
@@ -42,13 +49,10 @@ def _zeropower_via_newtonschulz5(G, steps):
42
  (2.8769, -3.1427, 1.2046),
43
  (2.8366, -3.0525, 1.2012),
44
  ]:
45
- A = X @ X.T
46
- # B = (
47
- # b * A + c * A @ A
48
- # )
49
- B = torch.addmm(A, A, A, alpha=c, beta=b)
50
- # X = a * X + B @ X
51
- X = torch.addmm(X, B, X, alpha=1.0, beta=a)
52
 
53
  if G.size(0) > G.size(1):
54
  X = X.T
@@ -69,51 +73,131 @@ class _muon_state:
69
  qk_clip_state = None
70
 
71
 
 
 
 
 
 
 
 
 
72
  @torch.no_grad()
73
- def _gather(p, state, rank, comm_stream, none_grad):
74
- """
75
- Gather the gradients to worker_rank.
76
- If none_grad is True, free p.grad after the gather.
77
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  with torch.cuda.stream(comm_stream):
79
- g = p.grad
 
80
 
81
- if rank == state.worker_rank:
82
- num_ranks = dist.get_world_size(group=state.process_group)
83
- gather_list = [
84
- torch.empty_like(g.to_local(), dtype=torch.bfloat16)
85
- for _ in range(num_ranks)
86
- ]
87
- else:
88
- gather_list = None
89
-
90
- g = g.to(torch.bfloat16)
91
- torch.distributed.gather(
92
- g.to_local(),
93
- dst=state.worker_rank,
94
- gather_list=gather_list,
95
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  )
97
- if rank == state.worker_rank:
98
- if state.gathered_grad is not None:
99
- raise RuntimeError(
100
- "Gather event already exists, which should not happen.")
101
- state.gathered_grad = torch.cat(gather_list, dim=0)
102
- state.gather_event = torch.cuda.Event()
103
- state.gather_event.record()
104
- else:
105
- state.gathered_grad = None
106
- state.gather_event = None
107
- gather_list = None
108
- if none_grad:
109
- # We can safely free p.grad without calling record_stream:
110
- # p.grad.to_local().record_stream(comm_stream)
111
- # Explanation:
112
- # 1. p.grad is created on the default stream, but the default stream
113
- # is synchronized with the comm stream later.
114
- # 2. There is no further activity on the default stream before the optimizer finishes.
115
- # Therefore, it is safe to free p.grad directly on the comm stream.
116
- p.grad = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
 
119
  @torch.no_grad()
@@ -127,45 +211,119 @@ def _compute_u(p, state, steps, rank, compute_stream):
127
  raise RuntimeError("Gather event must be set before compute.")
128
  compute_stream.wait_event(state.gather_event)
129
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
 
130
  state.computed_u = u
131
- state.scattered_u = torch.empty_like(p.to_local(),
132
- dtype=torch.bfloat16)
133
- state.compute_event = torch.cuda.Event()
134
- state.compute_event.record()
135
- u = None
136
 
137
 
138
  @torch.no_grad()
139
- def _scatter(p, state, rank, comm_stream):
140
- """
141
- Scatter the computed_u from worker_rank to all ranks.
142
- """
 
 
 
 
 
 
143
 
 
 
144
  with torch.cuda.stream(comm_stream):
145
- if state.compute_event is None:
146
- raise RuntimeError("Compute event must be set before scatter.")
147
- comm_stream.wait_event(state.compute_event)
 
 
148
 
149
- if rank == state.worker_rank:
150
- num_ranks = dist.get_world_size(group=state.process_group)
151
- # Clear the gathered gradient to free memory
152
- state.gathered_grad = None
153
 
154
- u = state.computed_u
155
- scatter_list = list(torch.split(u, p.size(0) // num_ranks, dim=0))
156
- scatter_list = [s.contiguous() for s in scatter_list]
157
- else:
158
- scatter_list = None
 
 
 
159
 
160
- torch.distributed.scatter(
161
- state.scattered_u,
162
- scatter_list=scatter_list,
163
- src=state.worker_rank,
164
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  )
166
- state.scatter_event = torch.cuda.Event()
167
- state.scatter_event.record()
168
- scatter_list = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
 
171
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
@@ -444,11 +602,14 @@ class Muon(torch.optim.Optimizer):
444
  if mesh is None:
445
  mesh = p.device_mesh
446
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
 
447
  elif mesh != p.device_mesh:
448
  raise ValueError("All parameters must be on the same mesh.")
449
 
 
450
  param_to_state[id(p)] = _muon_state()
451
- param_to_state[id(p)].worker_rank = shard_mesh[round_robin].item()
 
452
  param_to_state[id(p)].process_group = process_group
453
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
454
  param_to_state[id(p)].qk_clip_state = qk_clip_state
@@ -478,7 +639,7 @@ class Muon(torch.optim.Optimizer):
478
  else:
479
  g = buf
480
 
481
- u = _zeropower_via_newtonschulz5(g.bfloat16(),
482
  steps=group["ns_steps"])
483
 
484
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
@@ -493,15 +654,12 @@ class Muon(torch.optim.Optimizer):
493
  def _update_g(self, p, g, group, momentum):
494
  # calc update
495
  state = self.state[p]
496
- if "momentum_buffer" not in state:
497
- state["momentum_buffer"] = torch.zeros_like(g)
498
- buf = state["momentum_buffer"]
499
- buf.mul_(momentum).add_(g)
500
  if group["nesterov"]:
501
- g = g.add(buf, alpha=momentum)
502
- else:
503
- g = buf
504
- return g
505
 
506
  @staticmethod
507
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
@@ -585,11 +743,15 @@ class Muon(torch.optim.Optimizer):
585
  param_to_state, ordered_params = self.init_state_and_assign_params(
586
  names, params, group, qk_logits)
587
 
588
- def enqueue_gathers(start_idx, chunk_size):
589
- for p in ordered_params[start_idx:start_idx + chunk_size]:
590
- state = param_to_state[id(p)]
591
- _gather(p, state, self.rank, self.comm_stream,
592
- group["none_grad"])
 
 
 
 
593
 
594
  def enqueue_computes(start_idx, chunk_size):
595
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -597,10 +759,14 @@ class Muon(torch.optim.Optimizer):
597
  _compute_u(p, state, group["ns_steps"], self.rank,
598
  self.compute_stream)
599
 
600
- def enqueue_scatters(start_idx, chunk_size):
601
- for p in ordered_params[start_idx:start_idx + chunk_size]:
602
- state = param_to_state[id(p)]
603
- _scatter(p, state, self.rank, self.comm_stream)
 
 
 
 
604
 
605
  def enqueue_update_param(start_idx, chunk_size):
606
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -615,14 +781,16 @@ class Muon(torch.optim.Optimizer):
615
  # Wait grad update
616
  self.comm_stream.wait_stream(torch.cuda.current_stream())
617
 
618
- enqueue_gathers(0, chunk_size)
 
 
 
 
619
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
620
- enqueue_computes(i, chunk_size)
621
- if i > 0:
622
- enqueue_update_param(i - chunk_size, chunk_size)
623
- enqueue_gathers(i + chunk_size, chunk_size)
624
- enqueue_scatters(i, chunk_size)
625
- enqueue_update_param(i, chunk_size)
626
 
627
  # Wait the last update_param to finish
628
  torch.cuda.current_stream().wait_stream(self.compute_stream)
 
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
11
+ from .matmul_transpose_triton import matmul_transpose_assign
12
+
13
  logger = logging.getLogger(__name__)
14
 
15
+ COMM_DTYPE = torch.bfloat16
16
+
17
 
18
  # This code snippet is a modified version adapted from the following GitHub repositories:
19
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
20
  # Muon's Newton–Schulz iteration causes high variance in singular values
21
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
22
  @torch.no_grad()
23
+ # matmul_transpose_assign from : https://github.com/nil0x9/flash-muon
24
  def _zeropower_via_newtonschulz5(G, steps):
25
  """
26
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
 
32
  performance at all relative to UV^T, where USV^T = G is the SVD.
33
  """
34
  assert len(G.shape) == 2
35
+ assert G.dtype == COMM_DTYPE
36
  X = G # no manual typecast
37
 
38
  if G.size(0) > G.size(1):
39
  X = X.T
40
  # Ensure spectral norm is at most 1
41
  X = X / (X.norm() + 1e-7)
42
+ buf1 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
43
+ buf2 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
44
  # Perform the NS iterations
45
  for a, b, c in [
46
  (4.0848, -6.8946, 2.9270),
 
49
  (2.8769, -3.1427, 1.2046),
50
  (2.8366, -3.0525, 1.2012),
51
  ]:
52
+ matmul_transpose_assign(X, buf1)
53
+ matmul_transpose_assign(buf1, buf2)
54
+ buf1.mul_(b).add_(buf2, alpha=c)
55
+ X = torch.addmm(X, buf1, X, alpha=1.0, beta=a)
 
 
 
56
 
57
  if G.size(0) > G.size(1):
58
  X = X.T
 
73
  qk_clip_state = None
74
 
75
 
76
+ def split_elems_for_src(param, state, src_rank, num_ranks) -> int:
77
+ rows = param.shape[0]
78
+ cols = int(param.numel() // rows)
79
+ base, rem = divmod(rows, num_ranks)
80
+ my_rows = base + (1 if src_rank < rem else 0)
81
+ return my_rows * cols
82
+
83
+
84
  @torch.no_grad()
85
+ def _alloc_gathered_grad(params, param_to_state, rank, compute_stream):
86
+ with torch.cuda.stream(compute_stream):
87
+ for p in params:
88
+ state = param_to_state[id(p)]
89
+ if rank == state.worker_rank:
90
+ num_ranks = dist.get_world_size(group=state.process_group)
91
+ state.gathered_grad = torch.empty(p.grad.numel(),
92
+ dtype=COMM_DTYPE,
93
+ device="cuda")
94
+ else:
95
+ state.gathered_grad = None
96
+
97
+ alloc_event = torch.cuda.Event()
98
+ alloc_event.record(compute_stream)
99
+ return alloc_event
100
+
101
+
102
+ @torch.no_grad()
103
+ def _all2all_gather(params, param_to_state, rank, comm_stream, none_grad,
104
+ alloc_event):
105
  with torch.cuda.stream(comm_stream):
106
+ process_group = param_to_state[id(params[0])].process_group
107
+ num_ranks = dist.get_world_size(group=process_group)
108
 
109
+ # Calculate sending tensors
110
+ per_dst = [[] for _ in range(num_ranks)]
111
+ send_counts = [0] * num_ranks
112
+ for p in params:
113
+ state = param_to_state[id(p)]
114
+ dst = state.worker_rank
115
+ assert dst < num_ranks
116
+ shard_elems = split_elems_for_src(p, state, rank, num_ranks)
117
+ g = p.grad
118
+ g = g.to_local().to(COMM_DTYPE).contiguous().view(-1)
119
+ assert g.numel() == shard_elems
120
+ per_dst[dst].append(g)
121
+ send_counts[dst] += shard_elems
122
+
123
+ assert all(
124
+ len(v) > 0
125
+ for v in per_dst), "all params should be sharded to all devices"
126
+
127
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
128
+ owner_params = [
129
+ p for p in params if param_to_state[id(p)].worker_rank == rank
130
+ ]
131
+
132
+ # Calculate receiving tensors
133
+ recv_counts = [0] * num_ranks
134
+ for src in range(num_ranks):
135
+ total = 0
136
+ for p in owner_params:
137
+ state = param_to_state[id(p)]
138
+ assert state.worker_rank == rank
139
+ total += split_elems_for_src(p, state, src, num_ranks)
140
+ recv_counts[src] = total
141
+
142
+ recv_total = sum(recv_counts)
143
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
144
+ dist.all_to_all_single(
145
+ recv_buf,
146
+ send_buf,
147
+ output_split_sizes=recv_counts,
148
+ input_split_sizes=send_counts,
149
+ group=process_group,
150
  )
151
+
152
+ # Reconstructs gathered grad from the received buffer
153
+ #
154
+ # recv_buf (num ranks = 3)
155
+ #
156
+ # From rank 0 From rank 1 From rank 2
157
+ # | p1_0, p2_0, p3_0 | p1_1, p2_1, p3_1 | p1_2, p2_2, p3_2 |
158
+ #
159
+ # Outer loop:
160
+ # rank 0 -> rank 1 -> rank2
161
+ #
162
+ # Inner loop:
163
+ # p1_n -> p2_n -> p3_n
164
+
165
+ comm_stream.wait_event(alloc_event)
166
+
167
+ off = 0
168
+ write_offsets = {id(p): 0 for p in owner_params}
169
+ for src in range(num_ranks):
170
+ if recv_counts[src] == 0:
171
+ continue
172
+
173
+ block = recv_counts[src]
174
+ inner_off = 0
175
+ for p in owner_params:
176
+ state = param_to_state[id(p)]
177
+ assert state.worker_rank == rank
178
+ n = split_elems_for_src(p, state, src, num_ranks)
179
+ assert n > 0
180
+
181
+ sg = recv_buf.narrow(0, off + inner_off, n)
182
+ woff = write_offsets[id(p)]
183
+ dst = state.gathered_grad.narrow(0, woff, n)
184
+ dst.copy_(sg)
185
+
186
+ write_offsets[id(p)] += n
187
+ inner_off += n
188
+ off += block
189
+
190
+ for p in params:
191
+ state = param_to_state[id(p)]
192
+ if state.worker_rank == rank:
193
+ state.gathered_grad = state.gathered_grad.view_as(p)
194
+ state.gather_event = torch.cuda.Event()
195
+ state.gather_event.record(comm_stream)
196
+ else:
197
+ state.gathered_grad = None
198
+ state.gather_event = None
199
+ if none_grad:
200
+ p.grad = None
201
 
202
 
203
  @torch.no_grad()
 
211
  raise RuntimeError("Gather event must be set before compute.")
212
  compute_stream.wait_event(state.gather_event)
213
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
214
+ state.gathered_grad = None
215
  state.computed_u = u
216
+ state.compute_event = torch.cuda.Event()
217
+ state.compute_event.record()
218
+ else:
219
+ state.computed_u = None
220
+ state.compute_event = None
221
 
222
 
223
  @torch.no_grad()
224
+ def _alloc_scattered_u(params, param_to_state, rank, compute_stream):
225
+ with torch.cuda.stream(compute_stream):
226
+ for p in params:
227
+ state = param_to_state[id(p)]
228
+ state.scattered_u = torch.empty_like(p.to_local(),
229
+ dtype=COMM_DTYPE)
230
+
231
+ alloc_event = torch.cuda.Event()
232
+ alloc_event.record(compute_stream)
233
+ return alloc_event
234
 
235
+
236
+ def _all2all_scatter(params, param_to_state, rank, comm_stream, alloc_event):
237
  with torch.cuda.stream(comm_stream):
238
+ process_group = param_to_state[id(params[0])].process_group
239
+ num_ranks = dist.get_world_size(group=process_group)
240
+ owner_params = [
241
+ p for p in params if param_to_state[id(p)].worker_rank == rank
242
+ ]
243
 
244
+ per_dst = [[] for _ in range(num_ranks)]
245
+ send_counts = [0] * num_ranks
 
 
246
 
247
+ if owner_params:
248
+ for p in owner_params:
249
+ state = param_to_state[id(p)]
250
+ if state.compute_event is None:
251
+ raise RuntimeError(
252
+ "Compute event must be set before scatter.")
253
+ comm_stream.wait_event(state.compute_event)
254
+ state.gathered_grad = None
255
 
256
+ assert state.computed_u is not None
257
+
258
+ u_full = state.computed_u.to(COMM_DTYPE).contiguous().view(-1)
259
+
260
+ offset = 0
261
+ for dst in range(num_ranks):
262
+ n = split_elems_for_src(p, state, dst, num_ranks)
263
+ assert n > 0
264
+
265
+ su = u_full.narrow(0, offset, n)
266
+ per_dst[dst].append(su)
267
+ send_counts[dst] += n
268
+ offset += n
269
+
270
+ assert offset == u_full.numel()
271
+
272
+ if any(len(v) > 0 for v in per_dst):
273
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
274
+ else:
275
+ # all_to_all requires participation from all ranks
276
+ # Even non-owner ranks must join the collective call
277
+ send_buf = torch.empty(0, dtype=COMM_DTYPE, device="cuda")
278
+
279
+ recv_counts = [0] * num_ranks
280
+ for src in range(num_ranks):
281
+ total = 0
282
+ for p in params:
283
+ state = param_to_state[id(p)]
284
+ if state.worker_rank != src:
285
+ continue
286
+ total += split_elems_for_src(p, state, rank, num_ranks)
287
+ recv_counts[src] = total
288
+
289
+ recv_total = sum(recv_counts)
290
+ assert recv_total > 0
291
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
292
+
293
+ dist.all_to_all_single(
294
+ recv_buf,
295
+ send_buf,
296
+ output_split_sizes=recv_counts,
297
+ input_split_sizes=send_counts,
298
+ group=process_group,
299
  )
300
+
301
+ comm_stream.wait_event(alloc_event)
302
+
303
+ off = 0
304
+ for src in range(num_ranks):
305
+ block = recv_counts[src]
306
+ if block == 0:
307
+ continue
308
+
309
+ inner_off = 0
310
+ for p in params:
311
+ state = param_to_state[id(p)]
312
+ if state.worker_rank != src:
313
+ continue
314
+ n = split_elems_for_src(p, state, rank, num_ranks)
315
+ assert n > 0
316
+
317
+ flat_local = recv_buf.narrow(0, off + inner_off,
318
+ n).view_as(p.to_local())
319
+ state.scattered_u.copy_(flat_local)
320
+
321
+ state.scatter_event = torch.cuda.Event()
322
+ state.scatter_event.record(comm_stream)
323
+ inner_off += n
324
+
325
+ assert inner_off == block
326
+ off += block
327
 
328
 
329
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
 
602
  if mesh is None:
603
  mesh = p.device_mesh
604
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
605
+ self.rank = dist.get_rank(group=process_group)
606
  elif mesh != p.device_mesh:
607
  raise ValueError("All parameters must be on the same mesh.")
608
 
609
+ num_ranks = dist.get_world_size(group=process_group)
610
  param_to_state[id(p)] = _muon_state()
611
+ param_to_state[id(
612
+ p)].worker_rank = shard_mesh[round_robin].item() % num_ranks
613
  param_to_state[id(p)].process_group = process_group
614
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
615
  param_to_state[id(p)].qk_clip_state = qk_clip_state
 
639
  else:
640
  g = buf
641
 
642
+ u = _zeropower_via_newtonschulz5(g.to(COMM_DTYPE),
643
  steps=group["ns_steps"])
644
 
645
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
 
654
  def _update_g(self, p, g, group, momentum):
655
  # calc update
656
  state = self.state[p]
657
+ buf = state.setdefault("momentum_buffer", torch.zeros_like(g))
658
+ torch.add(g, buf, alpha=momentum, out=buf)
 
 
659
  if group["nesterov"]:
660
+ g.add_(buf, alpha=momentum)
661
+ return g
662
+ return buf
 
663
 
664
  @staticmethod
665
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
 
743
  param_to_state, ordered_params = self.init_state_and_assign_params(
744
  names, params, group, qk_logits)
745
 
746
+ def enqueue_all2all_gather(start_idx, chunk_size):
747
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
748
+ if target_params:
749
+ alloc_event = _alloc_gathered_grad(target_params,
750
+ param_to_state, self.rank,
751
+ self.compute_stream)
752
+ _all2all_gather(target_params, param_to_state, self.rank,
753
+ self.comm_stream, group["none_grad"],
754
+ alloc_event)
755
 
756
  def enqueue_computes(start_idx, chunk_size):
757
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
759
  _compute_u(p, state, group["ns_steps"], self.rank,
760
  self.compute_stream)
761
 
762
+ def enqueue_all2all_scatter(start_idx, chunk_size):
763
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
764
+ if target_params:
765
+ alloc_event = _alloc_scattered_u(target_params, param_to_state,
766
+ self.rank,
767
+ self.compute_stream)
768
+ _all2all_scatter(target_params, param_to_state, self.rank,
769
+ self.comm_stream, alloc_event)
770
 
771
  def enqueue_update_param(start_idx, chunk_size):
772
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
781
  # Wait grad update
782
  self.comm_stream.wait_stream(torch.cuda.current_stream())
783
 
784
+ PRE_STEP = 5
785
+ for i in range(0, PRE_STEP):
786
+ enqueue_all2all_gather(i * chunk_size, chunk_size)
787
+ enqueue_computes(i * chunk_size, chunk_size)
788
+
789
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
790
+ enqueue_all2all_scatter(i, chunk_size)
791
+ enqueue_all2all_gather(i + PRE_STEP * chunk_size, chunk_size)
792
+ enqueue_update_param(i, chunk_size)
793
+ enqueue_computes(i + PRE_STEP * chunk_size, chunk_size)
 
 
794
 
795
  # Wait the last update_param to finish
796
  torch.cuda.current_stream().wait_stream(self.compute_stream)
build/torch28-cxx11-rocm63-x86_64-linux/optimizer/_ops.py CHANGED
@@ -1,9 +1,9 @@
1
  import torch
2
- from . import _optimizer_9c21645_dirty
3
- ops = torch.ops._optimizer_9c21645_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
- return f"_optimizer_9c21645_dirty::{op_name}"
 
1
  import torch
2
+ from . import _optimizer_fceb334_dirty
3
+ ops = torch.ops._optimizer_fceb334_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
+ return f"_optimizer_fceb334_dirty::{op_name}"
build/torch28-cxx11-rocm63-x86_64-linux/optimizer/{_optimizer_9c21645_dirty.abi3.so → _optimizer_fceb334_dirty.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:d8f845b8df6426eb5db57e4525b8dd3c80004c44759b01a3e39cc37a817813b5
3
  size 1749936
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d45bbfe1dbab50f52d15893e65ec125af6101cf36f41576f9647326fdedc328b
3
  size 1749936
build/torch28-cxx11-rocm63-x86_64-linux/optimizer/matmul_transpose_triton.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import triton
3
+ import triton.language as tl
4
+
5
+
6
+ def get_autotune_config():
7
+ return [
8
+ triton.Config(
9
+ {
10
+ 'BLOCK_SIZE_M': blk_m,
11
+ 'BLOCK_SIZE_K': blk_k,
12
+ 'GROUP_SIZE_M': grp_sz
13
+ },
14
+ num_stages=n_stages,
15
+ num_warps=n_warps) for blk_m in [32, 64, 128]
16
+ for blk_k in [32, 64] for grp_sz in [8] for n_stages in [3, 4, 5]
17
+ for n_warps in [4, 8]
18
+ ]
19
+
20
+
21
+ @triton.autotune(
22
+ configs=get_autotune_config(),
23
+ key=['M', 'K'],
24
+ )
25
+ @triton.jit
26
+ def mmt_kernel(x, y, M, K, stride_xm, stride_xk, stride_ym, stride_yn,
27
+ BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_K: tl.constexpr,
28
+ GROUP_SIZE_M: tl.constexpr):
29
+ """
30
+ Core kernel jit function of matmul_transpose that computes y = x @ x.T
31
+ The code is a simple adaptation from the triton `matmul` tutorial:
32
+ https://triton-lang.org/main/getting-started/tutorials/03-matrix-multiplication.html
33
+ """
34
+ pid = tl.program_id(axis=0)
35
+ num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
36
+ num_pid_n = tl.cdiv(M, BLOCK_SIZE_M)
37
+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
38
+ group_id = pid // num_pid_in_group
39
+ first_pid_m = group_id * GROUP_SIZE_M
40
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
41
+ pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
42
+ pid_n = (pid % num_pid_in_group) // group_size_m
43
+ if pid_m > pid_n:
44
+ return
45
+
46
+ offs_xm = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
47
+ offs_xn = (pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
48
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
49
+ # we use a & b ptrs to denote different rows of x.
50
+ a_ptrs = x + (offs_xm[:, None] * stride_xm + offs_k[None, :] * stride_xk)
51
+ b_ptrs = x + (offs_xn[:, None] * stride_xm + offs_k[None, :] * stride_xk)
52
+
53
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_M), dtype=tl.float32)
54
+
55
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
56
+ a = tl.load(a_ptrs,
57
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
58
+ other=0.0)
59
+ b = tl.load(b_ptrs,
60
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
61
+ other=0.0)
62
+ accumulator = tl.dot(a, tl.permute(b, (1, 0)), accumulator)
63
+ a_ptrs += BLOCK_SIZE_K * stride_xk
64
+ b_ptrs += BLOCK_SIZE_K * stride_xk
65
+ # use dtype.element_ty to accommodate different input datatypes as in cpp templates
66
+ # https://github.com/triton-lang/triton/issues/2252
67
+ c = accumulator.to(x.dtype.element_ty)
68
+
69
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
70
+ offs_cn = pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
71
+ c_ptrs = y + stride_ym * offs_cm[:, None] + stride_yn * offs_cn[None, :]
72
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < M)
73
+ tl.store(c_ptrs, c, mask=c_mask)
74
+
75
+ # transpose and copy
76
+ if pid_m < pid_n:
77
+ ct_ptrs = y + stride_ym * offs_cn[:,
78
+ None] + stride_yn * offs_cm[None, :]
79
+ ct_mask = (offs_cn[:, None] < M) & (offs_cm[None, :] < M)
80
+ tl.store(ct_ptrs, tl.permute(c, (1, 0)), mask=ct_mask)
81
+
82
+
83
+ def matmul_transpose_assign(d_in, d_out):
84
+ assert d_in.is_cuda, "Input `d_in` must be a CUDA tensor"
85
+ assert d_out.is_cuda, "Input `d_out` must be a CUDA tensor"
86
+ assert d_in.device == d_out.device, "Inputs `d_in` and `d_out` must be on the same CUDA device"
87
+ assert d_in.dtype == d_out.dtype, "Inputs must have the same data type"
88
+ assert d_in.ndim == 2, "Input `d_in` must be a 2D tensor"
89
+ assert d_out.ndim == 2, "Input `d_out` must be a 2D tensor"
90
+ assert d_in.size(0) == d_out.size(0) == d_out.size(0), \
91
+ "First dimension of `d_in` must match first and second dimension of `d_out`"
92
+
93
+ d_in = d_in.contiguous()
94
+ M, K = d_in.shape
95
+ grid = lambda META: (triton.cdiv(M, META['BLOCK_SIZE_M']) * triton.cdiv(
96
+ M, META['BLOCK_SIZE_M']), )
97
+ with torch.cuda.device(d_in.device.index):
98
+ mmt_kernel[grid](d_in, d_out, M, K, d_in.stride(0), d_in.stride(1),
99
+ d_out.stride(0), d_out.stride(1))
100
+
101
+
102
+ def matmul_transpose(d_in):
103
+ M, _ = d_in.shape
104
+ d_out = torch.empty((M, M), device=d_in.device, dtype=d_in.dtype)
105
+ matmul_transpose_assign(d_in, d_out)
106
+ return d_out
build/torch28-cxx11-rocm63-x86_64-linux/optimizer/muon.py CHANGED
@@ -8,14 +8,19 @@ import torch
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
 
 
11
  logger = logging.getLogger(__name__)
12
 
 
 
13
 
14
  # This code snippet is a modified version adapted from the following GitHub repositories:
15
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
16
  # Muon's Newton–Schulz iteration causes high variance in singular values
17
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
18
  @torch.no_grad()
 
19
  def _zeropower_via_newtonschulz5(G, steps):
20
  """
21
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
@@ -27,13 +32,15 @@ def _zeropower_via_newtonschulz5(G, steps):
27
  performance at all relative to UV^T, where USV^T = G is the SVD.
28
  """
29
  assert len(G.shape) == 2
30
- assert G.dtype == torch.bfloat16
31
  X = G # no manual typecast
32
 
33
  if G.size(0) > G.size(1):
34
  X = X.T
35
  # Ensure spectral norm is at most 1
36
  X = X / (X.norm() + 1e-7)
 
 
37
  # Perform the NS iterations
38
  for a, b, c in [
39
  (4.0848, -6.8946, 2.9270),
@@ -42,13 +49,10 @@ def _zeropower_via_newtonschulz5(G, steps):
42
  (2.8769, -3.1427, 1.2046),
43
  (2.8366, -3.0525, 1.2012),
44
  ]:
45
- A = X @ X.T
46
- # B = (
47
- # b * A + c * A @ A
48
- # )
49
- B = torch.addmm(A, A, A, alpha=c, beta=b)
50
- # X = a * X + B @ X
51
- X = torch.addmm(X, B, X, alpha=1.0, beta=a)
52
 
53
  if G.size(0) > G.size(1):
54
  X = X.T
@@ -69,51 +73,131 @@ class _muon_state:
69
  qk_clip_state = None
70
 
71
 
 
 
 
 
 
 
 
 
72
  @torch.no_grad()
73
- def _gather(p, state, rank, comm_stream, none_grad):
74
- """
75
- Gather the gradients to worker_rank.
76
- If none_grad is True, free p.grad after the gather.
77
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  with torch.cuda.stream(comm_stream):
79
- g = p.grad
 
80
 
81
- if rank == state.worker_rank:
82
- num_ranks = dist.get_world_size(group=state.process_group)
83
- gather_list = [
84
- torch.empty_like(g.to_local(), dtype=torch.bfloat16)
85
- for _ in range(num_ranks)
86
- ]
87
- else:
88
- gather_list = None
89
-
90
- g = g.to(torch.bfloat16)
91
- torch.distributed.gather(
92
- g.to_local(),
93
- dst=state.worker_rank,
94
- gather_list=gather_list,
95
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  )
97
- if rank == state.worker_rank:
98
- if state.gathered_grad is not None:
99
- raise RuntimeError(
100
- "Gather event already exists, which should not happen.")
101
- state.gathered_grad = torch.cat(gather_list, dim=0)
102
- state.gather_event = torch.cuda.Event()
103
- state.gather_event.record()
104
- else:
105
- state.gathered_grad = None
106
- state.gather_event = None
107
- gather_list = None
108
- if none_grad:
109
- # We can safely free p.grad without calling record_stream:
110
- # p.grad.to_local().record_stream(comm_stream)
111
- # Explanation:
112
- # 1. p.grad is created on the default stream, but the default stream
113
- # is synchronized with the comm stream later.
114
- # 2. There is no further activity on the default stream before the optimizer finishes.
115
- # Therefore, it is safe to free p.grad directly on the comm stream.
116
- p.grad = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
 
119
  @torch.no_grad()
@@ -127,45 +211,119 @@ def _compute_u(p, state, steps, rank, compute_stream):
127
  raise RuntimeError("Gather event must be set before compute.")
128
  compute_stream.wait_event(state.gather_event)
129
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
 
130
  state.computed_u = u
131
- state.scattered_u = torch.empty_like(p.to_local(),
132
- dtype=torch.bfloat16)
133
- state.compute_event = torch.cuda.Event()
134
- state.compute_event.record()
135
- u = None
136
 
137
 
138
  @torch.no_grad()
139
- def _scatter(p, state, rank, comm_stream):
140
- """
141
- Scatter the computed_u from worker_rank to all ranks.
142
- """
 
 
 
 
 
 
143
 
 
 
144
  with torch.cuda.stream(comm_stream):
145
- if state.compute_event is None:
146
- raise RuntimeError("Compute event must be set before scatter.")
147
- comm_stream.wait_event(state.compute_event)
 
 
148
 
149
- if rank == state.worker_rank:
150
- num_ranks = dist.get_world_size(group=state.process_group)
151
- # Clear the gathered gradient to free memory
152
- state.gathered_grad = None
153
 
154
- u = state.computed_u
155
- scatter_list = list(torch.split(u, p.size(0) // num_ranks, dim=0))
156
- scatter_list = [s.contiguous() for s in scatter_list]
157
- else:
158
- scatter_list = None
 
 
 
159
 
160
- torch.distributed.scatter(
161
- state.scattered_u,
162
- scatter_list=scatter_list,
163
- src=state.worker_rank,
164
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  )
166
- state.scatter_event = torch.cuda.Event()
167
- state.scatter_event.record()
168
- scatter_list = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
 
171
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
@@ -444,11 +602,14 @@ class Muon(torch.optim.Optimizer):
444
  if mesh is None:
445
  mesh = p.device_mesh
446
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
 
447
  elif mesh != p.device_mesh:
448
  raise ValueError("All parameters must be on the same mesh.")
449
 
 
450
  param_to_state[id(p)] = _muon_state()
451
- param_to_state[id(p)].worker_rank = shard_mesh[round_robin].item()
 
452
  param_to_state[id(p)].process_group = process_group
453
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
454
  param_to_state[id(p)].qk_clip_state = qk_clip_state
@@ -478,7 +639,7 @@ class Muon(torch.optim.Optimizer):
478
  else:
479
  g = buf
480
 
481
- u = _zeropower_via_newtonschulz5(g.bfloat16(),
482
  steps=group["ns_steps"])
483
 
484
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
@@ -493,15 +654,12 @@ class Muon(torch.optim.Optimizer):
493
  def _update_g(self, p, g, group, momentum):
494
  # calc update
495
  state = self.state[p]
496
- if "momentum_buffer" not in state:
497
- state["momentum_buffer"] = torch.zeros_like(g)
498
- buf = state["momentum_buffer"]
499
- buf.mul_(momentum).add_(g)
500
  if group["nesterov"]:
501
- g = g.add(buf, alpha=momentum)
502
- else:
503
- g = buf
504
- return g
505
 
506
  @staticmethod
507
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
@@ -585,11 +743,15 @@ class Muon(torch.optim.Optimizer):
585
  param_to_state, ordered_params = self.init_state_and_assign_params(
586
  names, params, group, qk_logits)
587
 
588
- def enqueue_gathers(start_idx, chunk_size):
589
- for p in ordered_params[start_idx:start_idx + chunk_size]:
590
- state = param_to_state[id(p)]
591
- _gather(p, state, self.rank, self.comm_stream,
592
- group["none_grad"])
 
 
 
 
593
 
594
  def enqueue_computes(start_idx, chunk_size):
595
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -597,10 +759,14 @@ class Muon(torch.optim.Optimizer):
597
  _compute_u(p, state, group["ns_steps"], self.rank,
598
  self.compute_stream)
599
 
600
- def enqueue_scatters(start_idx, chunk_size):
601
- for p in ordered_params[start_idx:start_idx + chunk_size]:
602
- state = param_to_state[id(p)]
603
- _scatter(p, state, self.rank, self.comm_stream)
 
 
 
 
604
 
605
  def enqueue_update_param(start_idx, chunk_size):
606
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -615,14 +781,16 @@ class Muon(torch.optim.Optimizer):
615
  # Wait grad update
616
  self.comm_stream.wait_stream(torch.cuda.current_stream())
617
 
618
- enqueue_gathers(0, chunk_size)
 
 
 
 
619
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
620
- enqueue_computes(i, chunk_size)
621
- if i > 0:
622
- enqueue_update_param(i - chunk_size, chunk_size)
623
- enqueue_gathers(i + chunk_size, chunk_size)
624
- enqueue_scatters(i, chunk_size)
625
- enqueue_update_param(i, chunk_size)
626
 
627
  # Wait the last update_param to finish
628
  torch.cuda.current_stream().wait_stream(self.compute_stream)
 
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
11
+ from .matmul_transpose_triton import matmul_transpose_assign
12
+
13
  logger = logging.getLogger(__name__)
14
 
15
+ COMM_DTYPE = torch.bfloat16
16
+
17
 
18
  # This code snippet is a modified version adapted from the following GitHub repositories:
19
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
20
  # Muon's Newton–Schulz iteration causes high variance in singular values
21
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
22
  @torch.no_grad()
23
+ # matmul_transpose_assign from : https://github.com/nil0x9/flash-muon
24
  def _zeropower_via_newtonschulz5(G, steps):
25
  """
26
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
 
32
  performance at all relative to UV^T, where USV^T = G is the SVD.
33
  """
34
  assert len(G.shape) == 2
35
+ assert G.dtype == COMM_DTYPE
36
  X = G # no manual typecast
37
 
38
  if G.size(0) > G.size(1):
39
  X = X.T
40
  # Ensure spectral norm is at most 1
41
  X = X / (X.norm() + 1e-7)
42
+ buf1 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
43
+ buf2 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
44
  # Perform the NS iterations
45
  for a, b, c in [
46
  (4.0848, -6.8946, 2.9270),
 
49
  (2.8769, -3.1427, 1.2046),
50
  (2.8366, -3.0525, 1.2012),
51
  ]:
52
+ matmul_transpose_assign(X, buf1)
53
+ matmul_transpose_assign(buf1, buf2)
54
+ buf1.mul_(b).add_(buf2, alpha=c)
55
+ X = torch.addmm(X, buf1, X, alpha=1.0, beta=a)
 
 
 
56
 
57
  if G.size(0) > G.size(1):
58
  X = X.T
 
73
  qk_clip_state = None
74
 
75
 
76
+ def split_elems_for_src(param, state, src_rank, num_ranks) -> int:
77
+ rows = param.shape[0]
78
+ cols = int(param.numel() // rows)
79
+ base, rem = divmod(rows, num_ranks)
80
+ my_rows = base + (1 if src_rank < rem else 0)
81
+ return my_rows * cols
82
+
83
+
84
  @torch.no_grad()
85
+ def _alloc_gathered_grad(params, param_to_state, rank, compute_stream):
86
+ with torch.cuda.stream(compute_stream):
87
+ for p in params:
88
+ state = param_to_state[id(p)]
89
+ if rank == state.worker_rank:
90
+ num_ranks = dist.get_world_size(group=state.process_group)
91
+ state.gathered_grad = torch.empty(p.grad.numel(),
92
+ dtype=COMM_DTYPE,
93
+ device="cuda")
94
+ else:
95
+ state.gathered_grad = None
96
+
97
+ alloc_event = torch.cuda.Event()
98
+ alloc_event.record(compute_stream)
99
+ return alloc_event
100
+
101
+
102
+ @torch.no_grad()
103
+ def _all2all_gather(params, param_to_state, rank, comm_stream, none_grad,
104
+ alloc_event):
105
  with torch.cuda.stream(comm_stream):
106
+ process_group = param_to_state[id(params[0])].process_group
107
+ num_ranks = dist.get_world_size(group=process_group)
108
 
109
+ # Calculate sending tensors
110
+ per_dst = [[] for _ in range(num_ranks)]
111
+ send_counts = [0] * num_ranks
112
+ for p in params:
113
+ state = param_to_state[id(p)]
114
+ dst = state.worker_rank
115
+ assert dst < num_ranks
116
+ shard_elems = split_elems_for_src(p, state, rank, num_ranks)
117
+ g = p.grad
118
+ g = g.to_local().to(COMM_DTYPE).contiguous().view(-1)
119
+ assert g.numel() == shard_elems
120
+ per_dst[dst].append(g)
121
+ send_counts[dst] += shard_elems
122
+
123
+ assert all(
124
+ len(v) > 0
125
+ for v in per_dst), "all params should be sharded to all devices"
126
+
127
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
128
+ owner_params = [
129
+ p for p in params if param_to_state[id(p)].worker_rank == rank
130
+ ]
131
+
132
+ # Calculate receiving tensors
133
+ recv_counts = [0] * num_ranks
134
+ for src in range(num_ranks):
135
+ total = 0
136
+ for p in owner_params:
137
+ state = param_to_state[id(p)]
138
+ assert state.worker_rank == rank
139
+ total += split_elems_for_src(p, state, src, num_ranks)
140
+ recv_counts[src] = total
141
+
142
+ recv_total = sum(recv_counts)
143
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
144
+ dist.all_to_all_single(
145
+ recv_buf,
146
+ send_buf,
147
+ output_split_sizes=recv_counts,
148
+ input_split_sizes=send_counts,
149
+ group=process_group,
150
  )
151
+
152
+ # Reconstructs gathered grad from the received buffer
153
+ #
154
+ # recv_buf (num ranks = 3)
155
+ #
156
+ # From rank 0 From rank 1 From rank 2
157
+ # | p1_0, p2_0, p3_0 | p1_1, p2_1, p3_1 | p1_2, p2_2, p3_2 |
158
+ #
159
+ # Outer loop:
160
+ # rank 0 -> rank 1 -> rank2
161
+ #
162
+ # Inner loop:
163
+ # p1_n -> p2_n -> p3_n
164
+
165
+ comm_stream.wait_event(alloc_event)
166
+
167
+ off = 0
168
+ write_offsets = {id(p): 0 for p in owner_params}
169
+ for src in range(num_ranks):
170
+ if recv_counts[src] == 0:
171
+ continue
172
+
173
+ block = recv_counts[src]
174
+ inner_off = 0
175
+ for p in owner_params:
176
+ state = param_to_state[id(p)]
177
+ assert state.worker_rank == rank
178
+ n = split_elems_for_src(p, state, src, num_ranks)
179
+ assert n > 0
180
+
181
+ sg = recv_buf.narrow(0, off + inner_off, n)
182
+ woff = write_offsets[id(p)]
183
+ dst = state.gathered_grad.narrow(0, woff, n)
184
+ dst.copy_(sg)
185
+
186
+ write_offsets[id(p)] += n
187
+ inner_off += n
188
+ off += block
189
+
190
+ for p in params:
191
+ state = param_to_state[id(p)]
192
+ if state.worker_rank == rank:
193
+ state.gathered_grad = state.gathered_grad.view_as(p)
194
+ state.gather_event = torch.cuda.Event()
195
+ state.gather_event.record(comm_stream)
196
+ else:
197
+ state.gathered_grad = None
198
+ state.gather_event = None
199
+ if none_grad:
200
+ p.grad = None
201
 
202
 
203
  @torch.no_grad()
 
211
  raise RuntimeError("Gather event must be set before compute.")
212
  compute_stream.wait_event(state.gather_event)
213
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
214
+ state.gathered_grad = None
215
  state.computed_u = u
216
+ state.compute_event = torch.cuda.Event()
217
+ state.compute_event.record()
218
+ else:
219
+ state.computed_u = None
220
+ state.compute_event = None
221
 
222
 
223
  @torch.no_grad()
224
+ def _alloc_scattered_u(params, param_to_state, rank, compute_stream):
225
+ with torch.cuda.stream(compute_stream):
226
+ for p in params:
227
+ state = param_to_state[id(p)]
228
+ state.scattered_u = torch.empty_like(p.to_local(),
229
+ dtype=COMM_DTYPE)
230
+
231
+ alloc_event = torch.cuda.Event()
232
+ alloc_event.record(compute_stream)
233
+ return alloc_event
234
 
235
+
236
+ def _all2all_scatter(params, param_to_state, rank, comm_stream, alloc_event):
237
  with torch.cuda.stream(comm_stream):
238
+ process_group = param_to_state[id(params[0])].process_group
239
+ num_ranks = dist.get_world_size(group=process_group)
240
+ owner_params = [
241
+ p for p in params if param_to_state[id(p)].worker_rank == rank
242
+ ]
243
 
244
+ per_dst = [[] for _ in range(num_ranks)]
245
+ send_counts = [0] * num_ranks
 
 
246
 
247
+ if owner_params:
248
+ for p in owner_params:
249
+ state = param_to_state[id(p)]
250
+ if state.compute_event is None:
251
+ raise RuntimeError(
252
+ "Compute event must be set before scatter.")
253
+ comm_stream.wait_event(state.compute_event)
254
+ state.gathered_grad = None
255
 
256
+ assert state.computed_u is not None
257
+
258
+ u_full = state.computed_u.to(COMM_DTYPE).contiguous().view(-1)
259
+
260
+ offset = 0
261
+ for dst in range(num_ranks):
262
+ n = split_elems_for_src(p, state, dst, num_ranks)
263
+ assert n > 0
264
+
265
+ su = u_full.narrow(0, offset, n)
266
+ per_dst[dst].append(su)
267
+ send_counts[dst] += n
268
+ offset += n
269
+
270
+ assert offset == u_full.numel()
271
+
272
+ if any(len(v) > 0 for v in per_dst):
273
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
274
+ else:
275
+ # all_to_all requires participation from all ranks
276
+ # Even non-owner ranks must join the collective call
277
+ send_buf = torch.empty(0, dtype=COMM_DTYPE, device="cuda")
278
+
279
+ recv_counts = [0] * num_ranks
280
+ for src in range(num_ranks):
281
+ total = 0
282
+ for p in params:
283
+ state = param_to_state[id(p)]
284
+ if state.worker_rank != src:
285
+ continue
286
+ total += split_elems_for_src(p, state, rank, num_ranks)
287
+ recv_counts[src] = total
288
+
289
+ recv_total = sum(recv_counts)
290
+ assert recv_total > 0
291
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
292
+
293
+ dist.all_to_all_single(
294
+ recv_buf,
295
+ send_buf,
296
+ output_split_sizes=recv_counts,
297
+ input_split_sizes=send_counts,
298
+ group=process_group,
299
  )
300
+
301
+ comm_stream.wait_event(alloc_event)
302
+
303
+ off = 0
304
+ for src in range(num_ranks):
305
+ block = recv_counts[src]
306
+ if block == 0:
307
+ continue
308
+
309
+ inner_off = 0
310
+ for p in params:
311
+ state = param_to_state[id(p)]
312
+ if state.worker_rank != src:
313
+ continue
314
+ n = split_elems_for_src(p, state, rank, num_ranks)
315
+ assert n > 0
316
+
317
+ flat_local = recv_buf.narrow(0, off + inner_off,
318
+ n).view_as(p.to_local())
319
+ state.scattered_u.copy_(flat_local)
320
+
321
+ state.scatter_event = torch.cuda.Event()
322
+ state.scatter_event.record(comm_stream)
323
+ inner_off += n
324
+
325
+ assert inner_off == block
326
+ off += block
327
 
328
 
329
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
 
602
  if mesh is None:
603
  mesh = p.device_mesh
604
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
605
+ self.rank = dist.get_rank(group=process_group)
606
  elif mesh != p.device_mesh:
607
  raise ValueError("All parameters must be on the same mesh.")
608
 
609
+ num_ranks = dist.get_world_size(group=process_group)
610
  param_to_state[id(p)] = _muon_state()
611
+ param_to_state[id(
612
+ p)].worker_rank = shard_mesh[round_robin].item() % num_ranks
613
  param_to_state[id(p)].process_group = process_group
614
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
615
  param_to_state[id(p)].qk_clip_state = qk_clip_state
 
639
  else:
640
  g = buf
641
 
642
+ u = _zeropower_via_newtonschulz5(g.to(COMM_DTYPE),
643
  steps=group["ns_steps"])
644
 
645
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
 
654
  def _update_g(self, p, g, group, momentum):
655
  # calc update
656
  state = self.state[p]
657
+ buf = state.setdefault("momentum_buffer", torch.zeros_like(g))
658
+ torch.add(g, buf, alpha=momentum, out=buf)
 
 
659
  if group["nesterov"]:
660
+ g.add_(buf, alpha=momentum)
661
+ return g
662
+ return buf
 
663
 
664
  @staticmethod
665
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
 
743
  param_to_state, ordered_params = self.init_state_and_assign_params(
744
  names, params, group, qk_logits)
745
 
746
+ def enqueue_all2all_gather(start_idx, chunk_size):
747
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
748
+ if target_params:
749
+ alloc_event = _alloc_gathered_grad(target_params,
750
+ param_to_state, self.rank,
751
+ self.compute_stream)
752
+ _all2all_gather(target_params, param_to_state, self.rank,
753
+ self.comm_stream, group["none_grad"],
754
+ alloc_event)
755
 
756
  def enqueue_computes(start_idx, chunk_size):
757
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
759
  _compute_u(p, state, group["ns_steps"], self.rank,
760
  self.compute_stream)
761
 
762
+ def enqueue_all2all_scatter(start_idx, chunk_size):
763
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
764
+ if target_params:
765
+ alloc_event = _alloc_scattered_u(target_params, param_to_state,
766
+ self.rank,
767
+ self.compute_stream)
768
+ _all2all_scatter(target_params, param_to_state, self.rank,
769
+ self.comm_stream, alloc_event)
770
 
771
  def enqueue_update_param(start_idx, chunk_size):
772
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
781
  # Wait grad update
782
  self.comm_stream.wait_stream(torch.cuda.current_stream())
783
 
784
+ PRE_STEP = 5
785
+ for i in range(0, PRE_STEP):
786
+ enqueue_all2all_gather(i * chunk_size, chunk_size)
787
+ enqueue_computes(i * chunk_size, chunk_size)
788
+
789
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
790
+ enqueue_all2all_scatter(i, chunk_size)
791
+ enqueue_all2all_gather(i + PRE_STEP * chunk_size, chunk_size)
792
+ enqueue_update_param(i, chunk_size)
793
+ enqueue_computes(i + PRE_STEP * chunk_size, chunk_size)
 
 
794
 
795
  # Wait the last update_param to finish
796
  torch.cuda.current_stream().wait_stream(self.compute_stream)
build/torch28-cxx11-rocm64-x86_64-linux/optimizer/_ops.py CHANGED
@@ -1,9 +1,9 @@
1
  import torch
2
- from . import _optimizer_9c21645_dirty
3
- ops = torch.ops._optimizer_9c21645_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
- return f"_optimizer_9c21645_dirty::{op_name}"
 
1
  import torch
2
+ from . import _optimizer_fceb334_dirty
3
+ ops = torch.ops._optimizer_fceb334_dirty
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
  """
7
  Prefix op by namespace.
8
  """
9
+ return f"_optimizer_fceb334_dirty::{op_name}"
build/torch28-cxx11-rocm64-x86_64-linux/optimizer/{_optimizer_9c21645_dirty.abi3.so → _optimizer_fceb334_dirty.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:9a477575e3cc30e54d355b3e778240dc25fb0dab30362f3540dc5f925ac03ba1
3
  size 1750024
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7344e0b7800f75f7c80488e7096e23a4ecf22609fee805613ac77685e7f0893c
3
  size 1750024
build/torch28-cxx11-rocm64-x86_64-linux/optimizer/matmul_transpose_triton.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import triton
3
+ import triton.language as tl
4
+
5
+
6
+ def get_autotune_config():
7
+ return [
8
+ triton.Config(
9
+ {
10
+ 'BLOCK_SIZE_M': blk_m,
11
+ 'BLOCK_SIZE_K': blk_k,
12
+ 'GROUP_SIZE_M': grp_sz
13
+ },
14
+ num_stages=n_stages,
15
+ num_warps=n_warps) for blk_m in [32, 64, 128]
16
+ for blk_k in [32, 64] for grp_sz in [8] for n_stages in [3, 4, 5]
17
+ for n_warps in [4, 8]
18
+ ]
19
+
20
+
21
+ @triton.autotune(
22
+ configs=get_autotune_config(),
23
+ key=['M', 'K'],
24
+ )
25
+ @triton.jit
26
+ def mmt_kernel(x, y, M, K, stride_xm, stride_xk, stride_ym, stride_yn,
27
+ BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_K: tl.constexpr,
28
+ GROUP_SIZE_M: tl.constexpr):
29
+ """
30
+ Core kernel jit function of matmul_transpose that computes y = x @ x.T
31
+ The code is a simple adaptation from the triton `matmul` tutorial:
32
+ https://triton-lang.org/main/getting-started/tutorials/03-matrix-multiplication.html
33
+ """
34
+ pid = tl.program_id(axis=0)
35
+ num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
36
+ num_pid_n = tl.cdiv(M, BLOCK_SIZE_M)
37
+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
38
+ group_id = pid // num_pid_in_group
39
+ first_pid_m = group_id * GROUP_SIZE_M
40
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
41
+ pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
42
+ pid_n = (pid % num_pid_in_group) // group_size_m
43
+ if pid_m > pid_n:
44
+ return
45
+
46
+ offs_xm = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
47
+ offs_xn = (pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
48
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
49
+ # we use a & b ptrs to denote different rows of x.
50
+ a_ptrs = x + (offs_xm[:, None] * stride_xm + offs_k[None, :] * stride_xk)
51
+ b_ptrs = x + (offs_xn[:, None] * stride_xm + offs_k[None, :] * stride_xk)
52
+
53
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_M), dtype=tl.float32)
54
+
55
+ for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
56
+ a = tl.load(a_ptrs,
57
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
58
+ other=0.0)
59
+ b = tl.load(b_ptrs,
60
+ mask=offs_k[None, :] < K - k * BLOCK_SIZE_K,
61
+ other=0.0)
62
+ accumulator = tl.dot(a, tl.permute(b, (1, 0)), accumulator)
63
+ a_ptrs += BLOCK_SIZE_K * stride_xk
64
+ b_ptrs += BLOCK_SIZE_K * stride_xk
65
+ # use dtype.element_ty to accommodate different input datatypes as in cpp templates
66
+ # https://github.com/triton-lang/triton/issues/2252
67
+ c = accumulator.to(x.dtype.element_ty)
68
+
69
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
70
+ offs_cn = pid_n * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
71
+ c_ptrs = y + stride_ym * offs_cm[:, None] + stride_yn * offs_cn[None, :]
72
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < M)
73
+ tl.store(c_ptrs, c, mask=c_mask)
74
+
75
+ # transpose and copy
76
+ if pid_m < pid_n:
77
+ ct_ptrs = y + stride_ym * offs_cn[:,
78
+ None] + stride_yn * offs_cm[None, :]
79
+ ct_mask = (offs_cn[:, None] < M) & (offs_cm[None, :] < M)
80
+ tl.store(ct_ptrs, tl.permute(c, (1, 0)), mask=ct_mask)
81
+
82
+
83
+ def matmul_transpose_assign(d_in, d_out):
84
+ assert d_in.is_cuda, "Input `d_in` must be a CUDA tensor"
85
+ assert d_out.is_cuda, "Input `d_out` must be a CUDA tensor"
86
+ assert d_in.device == d_out.device, "Inputs `d_in` and `d_out` must be on the same CUDA device"
87
+ assert d_in.dtype == d_out.dtype, "Inputs must have the same data type"
88
+ assert d_in.ndim == 2, "Input `d_in` must be a 2D tensor"
89
+ assert d_out.ndim == 2, "Input `d_out` must be a 2D tensor"
90
+ assert d_in.size(0) == d_out.size(0) == d_out.size(0), \
91
+ "First dimension of `d_in` must match first and second dimension of `d_out`"
92
+
93
+ d_in = d_in.contiguous()
94
+ M, K = d_in.shape
95
+ grid = lambda META: (triton.cdiv(M, META['BLOCK_SIZE_M']) * triton.cdiv(
96
+ M, META['BLOCK_SIZE_M']), )
97
+ with torch.cuda.device(d_in.device.index):
98
+ mmt_kernel[grid](d_in, d_out, M, K, d_in.stride(0), d_in.stride(1),
99
+ d_out.stride(0), d_out.stride(1))
100
+
101
+
102
+ def matmul_transpose(d_in):
103
+ M, _ = d_in.shape
104
+ d_out = torch.empty((M, M), device=d_in.device, dtype=d_in.dtype)
105
+ matmul_transpose_assign(d_in, d_out)
106
+ return d_out
build/torch28-cxx11-rocm64-x86_64-linux/optimizer/muon.py CHANGED
@@ -8,14 +8,19 @@ import torch
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
 
 
11
  logger = logging.getLogger(__name__)
12
 
 
 
13
 
14
  # This code snippet is a modified version adapted from the following GitHub repositories:
15
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
16
  # Muon's Newton–Schulz iteration causes high variance in singular values
17
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
18
  @torch.no_grad()
 
19
  def _zeropower_via_newtonschulz5(G, steps):
20
  """
21
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
@@ -27,13 +32,15 @@ def _zeropower_via_newtonschulz5(G, steps):
27
  performance at all relative to UV^T, where USV^T = G is the SVD.
28
  """
29
  assert len(G.shape) == 2
30
- assert G.dtype == torch.bfloat16
31
  X = G # no manual typecast
32
 
33
  if G.size(0) > G.size(1):
34
  X = X.T
35
  # Ensure spectral norm is at most 1
36
  X = X / (X.norm() + 1e-7)
 
 
37
  # Perform the NS iterations
38
  for a, b, c in [
39
  (4.0848, -6.8946, 2.9270),
@@ -42,13 +49,10 @@ def _zeropower_via_newtonschulz5(G, steps):
42
  (2.8769, -3.1427, 1.2046),
43
  (2.8366, -3.0525, 1.2012),
44
  ]:
45
- A = X @ X.T
46
- # B = (
47
- # b * A + c * A @ A
48
- # )
49
- B = torch.addmm(A, A, A, alpha=c, beta=b)
50
- # X = a * X + B @ X
51
- X = torch.addmm(X, B, X, alpha=1.0, beta=a)
52
 
53
  if G.size(0) > G.size(1):
54
  X = X.T
@@ -69,51 +73,131 @@ class _muon_state:
69
  qk_clip_state = None
70
 
71
 
 
 
 
 
 
 
 
 
72
  @torch.no_grad()
73
- def _gather(p, state, rank, comm_stream, none_grad):
74
- """
75
- Gather the gradients to worker_rank.
76
- If none_grad is True, free p.grad after the gather.
77
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  with torch.cuda.stream(comm_stream):
79
- g = p.grad
 
80
 
81
- if rank == state.worker_rank:
82
- num_ranks = dist.get_world_size(group=state.process_group)
83
- gather_list = [
84
- torch.empty_like(g.to_local(), dtype=torch.bfloat16)
85
- for _ in range(num_ranks)
86
- ]
87
- else:
88
- gather_list = None
89
-
90
- g = g.to(torch.bfloat16)
91
- torch.distributed.gather(
92
- g.to_local(),
93
- dst=state.worker_rank,
94
- gather_list=gather_list,
95
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  )
97
- if rank == state.worker_rank:
98
- if state.gathered_grad is not None:
99
- raise RuntimeError(
100
- "Gather event already exists, which should not happen.")
101
- state.gathered_grad = torch.cat(gather_list, dim=0)
102
- state.gather_event = torch.cuda.Event()
103
- state.gather_event.record()
104
- else:
105
- state.gathered_grad = None
106
- state.gather_event = None
107
- gather_list = None
108
- if none_grad:
109
- # We can safely free p.grad without calling record_stream:
110
- # p.grad.to_local().record_stream(comm_stream)
111
- # Explanation:
112
- # 1. p.grad is created on the default stream, but the default stream
113
- # is synchronized with the comm stream later.
114
- # 2. There is no further activity on the default stream before the optimizer finishes.
115
- # Therefore, it is safe to free p.grad directly on the comm stream.
116
- p.grad = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
 
119
  @torch.no_grad()
@@ -127,45 +211,119 @@ def _compute_u(p, state, steps, rank, compute_stream):
127
  raise RuntimeError("Gather event must be set before compute.")
128
  compute_stream.wait_event(state.gather_event)
129
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
 
130
  state.computed_u = u
131
- state.scattered_u = torch.empty_like(p.to_local(),
132
- dtype=torch.bfloat16)
133
- state.compute_event = torch.cuda.Event()
134
- state.compute_event.record()
135
- u = None
136
 
137
 
138
  @torch.no_grad()
139
- def _scatter(p, state, rank, comm_stream):
140
- """
141
- Scatter the computed_u from worker_rank to all ranks.
142
- """
 
 
 
 
 
 
143
 
 
 
144
  with torch.cuda.stream(comm_stream):
145
- if state.compute_event is None:
146
- raise RuntimeError("Compute event must be set before scatter.")
147
- comm_stream.wait_event(state.compute_event)
 
 
148
 
149
- if rank == state.worker_rank:
150
- num_ranks = dist.get_world_size(group=state.process_group)
151
- # Clear the gathered gradient to free memory
152
- state.gathered_grad = None
153
 
154
- u = state.computed_u
155
- scatter_list = list(torch.split(u, p.size(0) // num_ranks, dim=0))
156
- scatter_list = [s.contiguous() for s in scatter_list]
157
- else:
158
- scatter_list = None
 
 
 
159
 
160
- torch.distributed.scatter(
161
- state.scattered_u,
162
- scatter_list=scatter_list,
163
- src=state.worker_rank,
164
- group=state.process_group,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  )
166
- state.scatter_event = torch.cuda.Event()
167
- state.scatter_event.record()
168
- scatter_list = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
 
171
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
@@ -444,11 +602,14 @@ class Muon(torch.optim.Optimizer):
444
  if mesh is None:
445
  mesh = p.device_mesh
446
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
 
447
  elif mesh != p.device_mesh:
448
  raise ValueError("All parameters must be on the same mesh.")
449
 
 
450
  param_to_state[id(p)] = _muon_state()
451
- param_to_state[id(p)].worker_rank = shard_mesh[round_robin].item()
 
452
  param_to_state[id(p)].process_group = process_group
453
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
454
  param_to_state[id(p)].qk_clip_state = qk_clip_state
@@ -478,7 +639,7 @@ class Muon(torch.optim.Optimizer):
478
  else:
479
  g = buf
480
 
481
- u = _zeropower_via_newtonschulz5(g.bfloat16(),
482
  steps=group["ns_steps"])
483
 
484
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
@@ -493,15 +654,12 @@ class Muon(torch.optim.Optimizer):
493
  def _update_g(self, p, g, group, momentum):
494
  # calc update
495
  state = self.state[p]
496
- if "momentum_buffer" not in state:
497
- state["momentum_buffer"] = torch.zeros_like(g)
498
- buf = state["momentum_buffer"]
499
- buf.mul_(momentum).add_(g)
500
  if group["nesterov"]:
501
- g = g.add(buf, alpha=momentum)
502
- else:
503
- g = buf
504
- return g
505
 
506
  @staticmethod
507
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
@@ -585,11 +743,15 @@ class Muon(torch.optim.Optimizer):
585
  param_to_state, ordered_params = self.init_state_and_assign_params(
586
  names, params, group, qk_logits)
587
 
588
- def enqueue_gathers(start_idx, chunk_size):
589
- for p in ordered_params[start_idx:start_idx + chunk_size]:
590
- state = param_to_state[id(p)]
591
- _gather(p, state, self.rank, self.comm_stream,
592
- group["none_grad"])
 
 
 
 
593
 
594
  def enqueue_computes(start_idx, chunk_size):
595
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -597,10 +759,14 @@ class Muon(torch.optim.Optimizer):
597
  _compute_u(p, state, group["ns_steps"], self.rank,
598
  self.compute_stream)
599
 
600
- def enqueue_scatters(start_idx, chunk_size):
601
- for p in ordered_params[start_idx:start_idx + chunk_size]:
602
- state = param_to_state[id(p)]
603
- _scatter(p, state, self.rank, self.comm_stream)
 
 
 
 
604
 
605
  def enqueue_update_param(start_idx, chunk_size):
606
  for p in ordered_params[start_idx:start_idx + chunk_size]:
@@ -615,14 +781,16 @@ class Muon(torch.optim.Optimizer):
615
  # Wait grad update
616
  self.comm_stream.wait_stream(torch.cuda.current_stream())
617
 
618
- enqueue_gathers(0, chunk_size)
 
 
 
 
619
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
620
- enqueue_computes(i, chunk_size)
621
- if i > 0:
622
- enqueue_update_param(i - chunk_size, chunk_size)
623
- enqueue_gathers(i + chunk_size, chunk_size)
624
- enqueue_scatters(i, chunk_size)
625
- enqueue_update_param(i, chunk_size)
626
 
627
  # Wait the last update_param to finish
628
  torch.cuda.current_stream().wait_stream(self.compute_stream)
 
8
  import torch.distributed as dist
9
  from torch.distributed._tensor import DTensor, Replicate, Shard
10
 
11
+ from .matmul_transpose_triton import matmul_transpose_assign
12
+
13
  logger = logging.getLogger(__name__)
14
 
15
+ COMM_DTYPE = torch.bfloat16
16
+
17
 
18
  # This code snippet is a modified version adapted from the following GitHub repositories:
19
  # https://github.com/KellerJordan/Muon/blob/master/muon.py
20
  # Muon's Newton–Schulz iteration causes high variance in singular values
21
  # Idea: give each iteration its own 3 coefficients and optimize them via gradient descent.
22
  @torch.no_grad()
23
+ # matmul_transpose_assign from : https://github.com/nil0x9/flash-muon
24
  def _zeropower_via_newtonschulz5(G, steps):
25
  """
26
  Newton-Schulz iteration to compute the zeroth power / orthogonalization of G. We opt to use a
 
32
  performance at all relative to UV^T, where USV^T = G is the SVD.
33
  """
34
  assert len(G.shape) == 2
35
+ assert G.dtype == COMM_DTYPE
36
  X = G # no manual typecast
37
 
38
  if G.size(0) > G.size(1):
39
  X = X.T
40
  # Ensure spectral norm is at most 1
41
  X = X / (X.norm() + 1e-7)
42
+ buf1 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
43
+ buf2 = torch.empty(X.size(0), X.size(0), dtype=X.dtype, device=X.device)
44
  # Perform the NS iterations
45
  for a, b, c in [
46
  (4.0848, -6.8946, 2.9270),
 
49
  (2.8769, -3.1427, 1.2046),
50
  (2.8366, -3.0525, 1.2012),
51
  ]:
52
+ matmul_transpose_assign(X, buf1)
53
+ matmul_transpose_assign(buf1, buf2)
54
+ buf1.mul_(b).add_(buf2, alpha=c)
55
+ X = torch.addmm(X, buf1, X, alpha=1.0, beta=a)
 
 
 
56
 
57
  if G.size(0) > G.size(1):
58
  X = X.T
 
73
  qk_clip_state = None
74
 
75
 
76
+ def split_elems_for_src(param, state, src_rank, num_ranks) -> int:
77
+ rows = param.shape[0]
78
+ cols = int(param.numel() // rows)
79
+ base, rem = divmod(rows, num_ranks)
80
+ my_rows = base + (1 if src_rank < rem else 0)
81
+ return my_rows * cols
82
+
83
+
84
  @torch.no_grad()
85
+ def _alloc_gathered_grad(params, param_to_state, rank, compute_stream):
86
+ with torch.cuda.stream(compute_stream):
87
+ for p in params:
88
+ state = param_to_state[id(p)]
89
+ if rank == state.worker_rank:
90
+ num_ranks = dist.get_world_size(group=state.process_group)
91
+ state.gathered_grad = torch.empty(p.grad.numel(),
92
+ dtype=COMM_DTYPE,
93
+ device="cuda")
94
+ else:
95
+ state.gathered_grad = None
96
+
97
+ alloc_event = torch.cuda.Event()
98
+ alloc_event.record(compute_stream)
99
+ return alloc_event
100
+
101
+
102
+ @torch.no_grad()
103
+ def _all2all_gather(params, param_to_state, rank, comm_stream, none_grad,
104
+ alloc_event):
105
  with torch.cuda.stream(comm_stream):
106
+ process_group = param_to_state[id(params[0])].process_group
107
+ num_ranks = dist.get_world_size(group=process_group)
108
 
109
+ # Calculate sending tensors
110
+ per_dst = [[] for _ in range(num_ranks)]
111
+ send_counts = [0] * num_ranks
112
+ for p in params:
113
+ state = param_to_state[id(p)]
114
+ dst = state.worker_rank
115
+ assert dst < num_ranks
116
+ shard_elems = split_elems_for_src(p, state, rank, num_ranks)
117
+ g = p.grad
118
+ g = g.to_local().to(COMM_DTYPE).contiguous().view(-1)
119
+ assert g.numel() == shard_elems
120
+ per_dst[dst].append(g)
121
+ send_counts[dst] += shard_elems
122
+
123
+ assert all(
124
+ len(v) > 0
125
+ for v in per_dst), "all params should be sharded to all devices"
126
+
127
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
128
+ owner_params = [
129
+ p for p in params if param_to_state[id(p)].worker_rank == rank
130
+ ]
131
+
132
+ # Calculate receiving tensors
133
+ recv_counts = [0] * num_ranks
134
+ for src in range(num_ranks):
135
+ total = 0
136
+ for p in owner_params:
137
+ state = param_to_state[id(p)]
138
+ assert state.worker_rank == rank
139
+ total += split_elems_for_src(p, state, src, num_ranks)
140
+ recv_counts[src] = total
141
+
142
+ recv_total = sum(recv_counts)
143
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
144
+ dist.all_to_all_single(
145
+ recv_buf,
146
+ send_buf,
147
+ output_split_sizes=recv_counts,
148
+ input_split_sizes=send_counts,
149
+ group=process_group,
150
  )
151
+
152
+ # Reconstructs gathered grad from the received buffer
153
+ #
154
+ # recv_buf (num ranks = 3)
155
+ #
156
+ # From rank 0 From rank 1 From rank 2
157
+ # | p1_0, p2_0, p3_0 | p1_1, p2_1, p3_1 | p1_2, p2_2, p3_2 |
158
+ #
159
+ # Outer loop:
160
+ # rank 0 -> rank 1 -> rank2
161
+ #
162
+ # Inner loop:
163
+ # p1_n -> p2_n -> p3_n
164
+
165
+ comm_stream.wait_event(alloc_event)
166
+
167
+ off = 0
168
+ write_offsets = {id(p): 0 for p in owner_params}
169
+ for src in range(num_ranks):
170
+ if recv_counts[src] == 0:
171
+ continue
172
+
173
+ block = recv_counts[src]
174
+ inner_off = 0
175
+ for p in owner_params:
176
+ state = param_to_state[id(p)]
177
+ assert state.worker_rank == rank
178
+ n = split_elems_for_src(p, state, src, num_ranks)
179
+ assert n > 0
180
+
181
+ sg = recv_buf.narrow(0, off + inner_off, n)
182
+ woff = write_offsets[id(p)]
183
+ dst = state.gathered_grad.narrow(0, woff, n)
184
+ dst.copy_(sg)
185
+
186
+ write_offsets[id(p)] += n
187
+ inner_off += n
188
+ off += block
189
+
190
+ for p in params:
191
+ state = param_to_state[id(p)]
192
+ if state.worker_rank == rank:
193
+ state.gathered_grad = state.gathered_grad.view_as(p)
194
+ state.gather_event = torch.cuda.Event()
195
+ state.gather_event.record(comm_stream)
196
+ else:
197
+ state.gathered_grad = None
198
+ state.gather_event = None
199
+ if none_grad:
200
+ p.grad = None
201
 
202
 
203
  @torch.no_grad()
 
211
  raise RuntimeError("Gather event must be set before compute.")
212
  compute_stream.wait_event(state.gather_event)
213
  u = _zeropower_via_newtonschulz5(state.gathered_grad, steps)
214
+ state.gathered_grad = None
215
  state.computed_u = u
216
+ state.compute_event = torch.cuda.Event()
217
+ state.compute_event.record()
218
+ else:
219
+ state.computed_u = None
220
+ state.compute_event = None
221
 
222
 
223
  @torch.no_grad()
224
+ def _alloc_scattered_u(params, param_to_state, rank, compute_stream):
225
+ with torch.cuda.stream(compute_stream):
226
+ for p in params:
227
+ state = param_to_state[id(p)]
228
+ state.scattered_u = torch.empty_like(p.to_local(),
229
+ dtype=COMM_DTYPE)
230
+
231
+ alloc_event = torch.cuda.Event()
232
+ alloc_event.record(compute_stream)
233
+ return alloc_event
234
 
235
+
236
+ def _all2all_scatter(params, param_to_state, rank, comm_stream, alloc_event):
237
  with torch.cuda.stream(comm_stream):
238
+ process_group = param_to_state[id(params[0])].process_group
239
+ num_ranks = dist.get_world_size(group=process_group)
240
+ owner_params = [
241
+ p for p in params if param_to_state[id(p)].worker_rank == rank
242
+ ]
243
 
244
+ per_dst = [[] for _ in range(num_ranks)]
245
+ send_counts = [0] * num_ranks
 
 
246
 
247
+ if owner_params:
248
+ for p in owner_params:
249
+ state = param_to_state[id(p)]
250
+ if state.compute_event is None:
251
+ raise RuntimeError(
252
+ "Compute event must be set before scatter.")
253
+ comm_stream.wait_event(state.compute_event)
254
+ state.gathered_grad = None
255
 
256
+ assert state.computed_u is not None
257
+
258
+ u_full = state.computed_u.to(COMM_DTYPE).contiguous().view(-1)
259
+
260
+ offset = 0
261
+ for dst in range(num_ranks):
262
+ n = split_elems_for_src(p, state, dst, num_ranks)
263
+ assert n > 0
264
+
265
+ su = u_full.narrow(0, offset, n)
266
+ per_dst[dst].append(su)
267
+ send_counts[dst] += n
268
+ offset += n
269
+
270
+ assert offset == u_full.numel()
271
+
272
+ if any(len(v) > 0 for v in per_dst):
273
+ send_buf = torch.cat([torch.cat(v, dim=0) for v in per_dst], dim=0)
274
+ else:
275
+ # all_to_all requires participation from all ranks
276
+ # Even non-owner ranks must join the collective call
277
+ send_buf = torch.empty(0, dtype=COMM_DTYPE, device="cuda")
278
+
279
+ recv_counts = [0] * num_ranks
280
+ for src in range(num_ranks):
281
+ total = 0
282
+ for p in params:
283
+ state = param_to_state[id(p)]
284
+ if state.worker_rank != src:
285
+ continue
286
+ total += split_elems_for_src(p, state, rank, num_ranks)
287
+ recv_counts[src] = total
288
+
289
+ recv_total = sum(recv_counts)
290
+ assert recv_total > 0
291
+ recv_buf = torch.empty(recv_total, dtype=COMM_DTYPE, device="cuda")
292
+
293
+ dist.all_to_all_single(
294
+ recv_buf,
295
+ send_buf,
296
+ output_split_sizes=recv_counts,
297
+ input_split_sizes=send_counts,
298
+ group=process_group,
299
  )
300
+
301
+ comm_stream.wait_event(alloc_event)
302
+
303
+ off = 0
304
+ for src in range(num_ranks):
305
+ block = recv_counts[src]
306
+ if block == 0:
307
+ continue
308
+
309
+ inner_off = 0
310
+ for p in params:
311
+ state = param_to_state[id(p)]
312
+ if state.worker_rank != src:
313
+ continue
314
+ n = split_elems_for_src(p, state, rank, num_ranks)
315
+ assert n > 0
316
+
317
+ flat_local = recv_buf.narrow(0, off + inner_off,
318
+ n).view_as(p.to_local())
319
+ state.scattered_u.copy_(flat_local)
320
+
321
+ state.scatter_event = torch.cuda.Event()
322
+ state.scatter_event.record(comm_stream)
323
+ inner_off += n
324
+
325
+ assert inner_off == block
326
+ off += block
327
 
328
 
329
  def _update_param(p, state, lr, adjusted_lr, weight_decay, rank,
 
602
  if mesh is None:
603
  mesh = p.device_mesh
604
  shard_mesh, process_group = self.get_shard_mesh(p, self.rank)
605
+ self.rank = dist.get_rank(group=process_group)
606
  elif mesh != p.device_mesh:
607
  raise ValueError("All parameters must be on the same mesh.")
608
 
609
+ num_ranks = dist.get_world_size(group=process_group)
610
  param_to_state[id(p)] = _muon_state()
611
+ param_to_state[id(
612
+ p)].worker_rank = shard_mesh[round_robin].item() % num_ranks
613
  param_to_state[id(p)].process_group = process_group
614
  qk_clip_state = self.get_qk_clip_info(n, qk_logits)
615
  param_to_state[id(p)].qk_clip_state = qk_clip_state
 
639
  else:
640
  g = buf
641
 
642
+ u = _zeropower_via_newtonschulz5(g.to(COMM_DTYPE),
643
  steps=group["ns_steps"])
644
 
645
  adjusted_lr = self.adjust_lr_for_muon(lr, p.shape)
 
654
  def _update_g(self, p, g, group, momentum):
655
  # calc update
656
  state = self.state[p]
657
+ buf = state.setdefault("momentum_buffer", torch.zeros_like(g))
658
+ torch.add(g, buf, alpha=momentum, out=buf)
 
 
659
  if group["nesterov"]:
660
+ g.add_(buf, alpha=momentum)
661
+ return g
662
+ return buf
 
663
 
664
  @staticmethod
665
  def _update_p(p, u, lr, adjusted_lr, weight_decay):
 
743
  param_to_state, ordered_params = self.init_state_and_assign_params(
744
  names, params, group, qk_logits)
745
 
746
+ def enqueue_all2all_gather(start_idx, chunk_size):
747
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
748
+ if target_params:
749
+ alloc_event = _alloc_gathered_grad(target_params,
750
+ param_to_state, self.rank,
751
+ self.compute_stream)
752
+ _all2all_gather(target_params, param_to_state, self.rank,
753
+ self.comm_stream, group["none_grad"],
754
+ alloc_event)
755
 
756
  def enqueue_computes(start_idx, chunk_size):
757
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
759
  _compute_u(p, state, group["ns_steps"], self.rank,
760
  self.compute_stream)
761
 
762
+ def enqueue_all2all_scatter(start_idx, chunk_size):
763
+ target_params = ordered_params[start_idx:start_idx + chunk_size]
764
+ if target_params:
765
+ alloc_event = _alloc_scattered_u(target_params, param_to_state,
766
+ self.rank,
767
+ self.compute_stream)
768
+ _all2all_scatter(target_params, param_to_state, self.rank,
769
+ self.comm_stream, alloc_event)
770
 
771
  def enqueue_update_param(start_idx, chunk_size):
772
  for p in ordered_params[start_idx:start_idx + chunk_size]:
 
781
  # Wait grad update
782
  self.comm_stream.wait_stream(torch.cuda.current_stream())
783
 
784
+ PRE_STEP = 5
785
+ for i in range(0, PRE_STEP):
786
+ enqueue_all2all_gather(i * chunk_size, chunk_size)
787
+ enqueue_computes(i * chunk_size, chunk_size)
788
+
789
  for i in range(0, len(params) + chunk_size - 1, chunk_size):
790
+ enqueue_all2all_scatter(i, chunk_size)
791
+ enqueue_all2all_gather(i + PRE_STEP * chunk_size, chunk_size)
792
+ enqueue_update_param(i, chunk_size)
793
+ enqueue_computes(i + PRE_STEP * chunk_size, chunk_size)
 
 
794
 
795
  # Wait the last update_param to finish
796
  torch.cuda.current_stream().wait_stream(self.compute_stream)