Kernels
wyldecat Claude Opus 4.6 commited on
Commit
8ca2492
·
1 Parent(s): e74d98f

Cache AdamW placement grouping and tensor lists [skip-build]

Browse files

- DTensor unwrap via _to_local() to eliminate __torch_dispatch__ overhead
- _placement_cache: group params by placement once, reuse every step
- _tensor_cache: cache (params_local, moment1, moment2, state_steps)
- Only gradients collected fresh each step
- Profiler annotations for AdamW stages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. torch-ext/optimizer/adamw.py +154 -37
torch-ext/optimizer/adamw.py CHANGED
@@ -1,8 +1,12 @@
 
1
  from collections import defaultdict
2
  from typing import cast
3
 
4
  import torch
5
  from torch.distributed.tensor import DTensor
 
 
 
6
 
7
 
8
  def fused_adamw(
@@ -72,54 +76,72 @@ def fused_adamw(
72
  )
73
 
74
 
75
- def step_adamw_params(optimizer_state, params, group):
76
- """Run fused AdamW on a list of parameters sharing the same placement.
 
77
 
78
- Args:
79
- optimizer_state: The optimizer's state dict (self.state in Muon).
80
- params: List of parameters to update.
81
- group: Parameter group dict with lr, adamw_betas, adamw_eps, weight_decay.
82
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  params_with_grads = []
84
  grads = []
85
  moment1 = []
86
  moment2 = []
87
- max_exp_avg_sqs = []
88
  state_steps = []
89
- lr = group["lr"]
90
- beta1, beta2 = group["adamw_betas"]
91
- eps = group["adamw_eps"]
92
- weight_decay = group["weight_decay"]
93
 
94
  for p in params:
95
  g = p.grad
96
  if g is None:
97
  continue
98
  state = optimizer_state[p]
99
- params_with_grads.append(p)
100
- grads.append(g)
101
  if "step" not in state:
102
- state["step"] = (torch.zeros((),
103
- dtype=torch.float32,
104
- device=p.device))
105
  state["moment1"] = torch.zeros_like(g)
106
  state["moment2"] = torch.zeros_like(g)
107
- moment1.append(state["moment1"])
108
- moment2.append(state["moment2"])
109
  if not isinstance(state["step"], torch.Tensor):
110
- step_tensor = torch.tensor(state["step"],
111
- dtype=torch.float32,
112
- device=p.device)
113
- else:
114
- step_tensor = state["step"]
115
- state_steps.append(step_tensor)
 
 
 
 
 
 
116
 
117
  fused_adamw(
118
  params_with_grads,
119
  grads,
120
  moment1,
121
  moment2,
122
- max_exp_avg_sqs,
123
  state_steps,
124
  amsgrad=False,
125
  beta1=beta1,
@@ -131,24 +153,119 @@ def step_adamw_params(optimizer_state, params, group):
131
  )
132
 
133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  def step_adamw(optimizer_state, group):
135
  """Dispatch AdamW step, grouping parameters by type and placement.
136
 
 
 
 
137
  Args:
138
  optimizer_state: The optimizer's state dict (self.state in Muon).
139
  group: Parameter group dict.
140
  """
141
  params = group["params"]
 
142
 
143
- # group params with its type and placement
144
- placement_to_params: dict[tuple, list[torch.Tensor]] = defaultdict(list)
145
- for p in params:
146
- match p:
147
- case DTensor():
148
- placement_to_params[tuple([p.placements,
149
- p.device_mesh])].append(p)
150
- case torch.Tensor():
151
- placement_to_params[tuple([torch.Tensor, None])].append(p)
152
-
153
- for group_params in placement_to_params.values():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  step_adamw_params(optimizer_state, group_params, group)
 
1
+ import logging
2
  from collections import defaultdict
3
  from typing import cast
4
 
5
  import torch
6
  from torch.distributed.tensor import DTensor
7
+ from torch.profiler import record_function
8
+
9
+ logger = logging.getLogger(__name__)
10
 
11
 
12
  def fused_adamw(
 
76
  )
77
 
78
 
79
+ def _to_local(t):
80
+ """Unwrap DTensor to local tensor for fused ops."""
81
+ return t._local_tensor if isinstance(t, DTensor) else t
82
 
83
+
84
+ # ---------------------------------------------------------------------------
85
+ # Caches for eliminating per-step Python overhead.
86
+ #
87
+ # Placement grouping and tensor list assembly are identical every step
88
+ # (params don't change placement, moment/step tensors are the same objects
89
+ # after initialisation). We cache them keyed by id() of the param list
90
+ # stored in param_groups (stable across steps).
91
+ #
92
+ # Only gradients change each step and must be collected fresh.
93
+ # ---------------------------------------------------------------------------
94
+
95
+ # id(group["params"]) → dict[placement_key, list[param]]
96
+ _placement_cache: dict[int, dict[tuple, list]] = {}
97
+
98
+ # id(placement_group_list) → (params_local, moment1, moment2, state_steps)
99
+ _tensor_cache: dict[int, tuple[list, list, list, list]] = {}
100
+
101
+
102
+ def _step_adamw_params_slow(optimizer_state, params, group):
103
+ """Uncached fallback for the rare case where some params lack grads."""
104
  params_with_grads = []
105
  grads = []
106
  moment1 = []
107
  moment2 = []
 
108
  state_steps = []
 
 
 
 
109
 
110
  for p in params:
111
  g = p.grad
112
  if g is None:
113
  continue
114
  state = optimizer_state[p]
115
+ params_with_grads.append(_to_local(p))
116
+ grads.append(_to_local(g))
117
  if "step" not in state:
118
+ state["step"] = torch.zeros((),
119
+ dtype=torch.float32,
120
+ device=p.device)
121
  state["moment1"] = torch.zeros_like(g)
122
  state["moment2"] = torch.zeros_like(g)
123
+ moment1.append(_to_local(state["moment1"]))
124
+ moment2.append(_to_local(state["moment2"]))
125
  if not isinstance(state["step"], torch.Tensor):
126
+ state["step"] = torch.tensor(state["step"],
127
+ dtype=torch.float32,
128
+ device=p.device)
129
+ state_steps.append(state["step"])
130
+
131
+ if not params_with_grads:
132
+ return
133
+
134
+ lr = group["lr"]
135
+ beta1, beta2 = group["adamw_betas"]
136
+ eps = group["adamw_eps"]
137
+ weight_decay = group["weight_decay"]
138
 
139
  fused_adamw(
140
  params_with_grads,
141
  grads,
142
  moment1,
143
  moment2,
144
+ [],
145
  state_steps,
146
  amsgrad=False,
147
  beta1=beta1,
 
153
  )
154
 
155
 
156
+ def step_adamw_params(optimizer_state, params, group):
157
+ """Run fused AdamW on a list of parameters sharing the same placement.
158
+
159
+ After the first call, cached tensor lists (params_local, moment1,
160
+ moment2, state_steps) are reused — only gradients are collected fresh.
161
+
162
+ Args:
163
+ optimizer_state: The optimizer's state dict (self.state in Muon).
164
+ params: List of parameters to update.
165
+ group: Parameter group dict with lr, adamw_betas, adamw_eps, weight_decay.
166
+ """
167
+ # Collect grads — the only thing that changes each step.
168
+ with record_function("adamw::collect_grads"):
169
+ grads = []
170
+ for p in params:
171
+ g = p.grad
172
+ if g is None:
173
+ # Rare: fall back to slow path that filters per-param.
174
+ _step_adamw_params_slow(optimizer_state, params, group)
175
+ return
176
+ grads.append(_to_local(g))
177
+
178
+ tensor_key = id(params)
179
+ if tensor_key not in _tensor_cache:
180
+ with record_function("adamw::init_tensor_cache"):
181
+ params_local = []
182
+ moment1 = []
183
+ moment2 = []
184
+ state_steps = []
185
+
186
+ for p in params:
187
+ state = optimizer_state[p]
188
+ params_local.append(_to_local(p))
189
+ if "step" not in state:
190
+ state["step"] = torch.zeros((),
191
+ dtype=torch.float32,
192
+ device=p.device)
193
+ state["moment1"] = torch.zeros_like(p.grad)
194
+ state["moment2"] = torch.zeros_like(p.grad)
195
+ moment1.append(_to_local(state["moment1"]))
196
+ moment2.append(_to_local(state["moment2"]))
197
+ if not isinstance(state["step"], torch.Tensor):
198
+ state["step"] = torch.tensor(state["step"],
199
+ dtype=torch.float32,
200
+ device=p.device)
201
+ state_steps.append(state["step"])
202
+
203
+ _tensor_cache[tensor_key] = (params_local, moment1, moment2,
204
+ state_steps)
205
+
206
+ params_local, moment1, moment2, state_steps = _tensor_cache[tensor_key]
207
+
208
+ lr = group["lr"]
209
+ beta1, beta2 = group["adamw_betas"]
210
+ eps = group["adamw_eps"]
211
+ weight_decay = group["weight_decay"]
212
+
213
+ with record_function("adamw::fused_adamw"):
214
+ fused_adamw(
215
+ params_local,
216
+ grads,
217
+ moment1,
218
+ moment2,
219
+ [],
220
+ state_steps,
221
+ amsgrad=False,
222
+ beta1=beta1,
223
+ beta2=beta2,
224
+ lr=lr,
225
+ weight_decay=weight_decay,
226
+ eps=eps,
227
+ maximize=False,
228
+ )
229
+
230
+
231
  def step_adamw(optimizer_state, group):
232
  """Dispatch AdamW step, grouping parameters by type and placement.
233
 
234
+ Placement grouping is cached after the first call since params never
235
+ change their placement between steps.
236
+
237
  Args:
238
  optimizer_state: The optimizer's state dict (self.state in Muon).
239
  group: Parameter group dict.
240
  """
241
  params = group["params"]
242
+ placement_key = id(params)
243
 
244
+ if placement_key not in _placement_cache:
245
+ with record_function("adamw::group_by_placement"):
246
+ placement_to_params: dict[tuple,
247
+ list[torch.Tensor]] = defaultdict(list)
248
+ for p in params:
249
+ match p:
250
+ case DTensor():
251
+ logger.debug(
252
+ "[AdamW] DTensor param: shape=%s, placements=%s, "
253
+ "mesh=%s, grad=%s", p.shape, p.placements,
254
+ p.device_mesh.mesh_dim_names,
255
+ p.grad.shape if p.grad is not None else None)
256
+ placement_to_params[tuple(
257
+ [p.placements, p.device_mesh])].append(p)
258
+ case torch.Tensor():
259
+ logger.debug(
260
+ "[AdamW] plain param: shape=%s, grad=%s", p.shape,
261
+ p.grad.shape if p.grad is not None else None)
262
+ placement_to_params[tuple([torch.Tensor,
263
+ None])].append(p)
264
+
265
+ logger.debug("[AdamW] %d placement groups, %d total params",
266
+ len(placement_to_params), len(params))
267
+
268
+ _placement_cache[placement_key] = dict(placement_to_params)
269
+
270
+ for group_params in _placement_cache[placement_key].values():
271
  step_adamw_params(optimizer_state, group_params, group)