Skip to content

Commit 045cfd6

Browse files
Optimization fused adam multi tensor impl for flagos and torch
1 parent 3ad85b9 commit 045cfd6

4 files changed

Lines changed: 226 additions & 164 deletions

File tree

transformer_engine/plugin/core/backends/flagos/flagos.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def multi_tensor_adam(
163163
return multi_tensor_adam_fl
164164
return multi_tensor_adam_fl(
165165
chunk_size=chunk_size, noop_flag=noop_flag, tensor_lists=tensor_lists,
166-
lr=lr, beta1=beta1, beta2=beta2, eps=epsilon,
166+
lr=lr, beta1=beta1, beta2=beta2, epsilon=epsilon,
167167
step=step, mode=mode, bias_correction=bias_correction, weight_decay=weight_decay,
168168
)
169169
def multi_tensor_adam_param_remainder(
@@ -184,7 +184,7 @@ def multi_tensor_adam_param_remainder(
184184
return multi_tensor_adam_param_remainder_fl
185185
return multi_tensor_adam_param_remainder_fl(
186186
chunk_size=chunk_size, noop_flag=noop_flag, tensor_lists=tensor_lists,
187-
lr=lr, beta1=beta1, beta2=beta2, eps=epsilon,
187+
lr=lr, beta1=beta1, beta2=beta2, epsilon=epsilon,
188188
step=step, mode=mode, bias_correction=bias_correction, weight_decay=weight_decay,
189189
)
190190

transformer_engine/plugin/core/backends/flagos/impl/fused_adam.py

Lines changed: 96 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,20 @@ def multi_tensor_adam_fl(
1414
lr: float,
1515
beta1: float,
1616
beta2: float,
17-
eps: float,
17+
epsilon: float,
1818
step: int,
1919
mode: int,
2020
bias_correction: int,
2121
weight_decay: float,
2222
inv_scale: Optional[float] = 1.0,
2323
out_dtype: Optional[torch.dtype] = None,
2424
) -> None:
25+
"""
26+
Adam optimizer implementation matching CUDA exactly.
2527
28+
mode == 0: L2 regularization (add weight_decay * param to gradient before moment update)
29+
mode == 1: AdamW (add weight_decay * param to update after moment computation)
30+
"""
2631
num_lists = len(tensor_lists)
2732
assert num_lists in [4, 5], f"Expected 4 or 5 tensor lists, got {num_lists}"
2833

@@ -50,66 +55,67 @@ def multi_tensor_adam_fl(
5055
if not g.is_contiguous():
5156
g = g.contiguous()
5257

58+
# Convert to float for computation (matches CUDA's MATH_T = float)
59+
g = g.float()
60+
p_float = p.float()
61+
5362
if inv_scale is not None and inv_scale != 1.0:
5463
g = flag_gems.mul(g, inv_scale)
5564

56-
m = flag_gems.add_(flag_gems.mul_(m, beta1), g, alpha=1-beta1)
57-
v = flag_gems.add_(flag_gems.mul_(v, beta2), flag_gems.mul_(flag_gems.mul_(g, g), 1 - beta2))
65+
if mode == 0: # L2 regularization
66+
# Add weight decay to gradient before moment update
67+
g = flag_gems.add(g, p_float, alpha=weight_decay)
68+
69+
# Update moments with modified gradient
70+
flag_gems.add_(flag_gems.mul_(m, beta1), g, alpha=1-beta1)
71+
flag_gems.add_(flag_gems.mul_(v, beta2), flag_gems.mul(g, g), alpha=1-beta2)
72+
73+
# Bias correction
74+
m_corr = flag_gems.true_divide(m, bias_correction1)
75+
v_corr = flag_gems.true_divide(v, bias_correction2)
5876

59-
m_corr = m.clone()
60-
v_corr = v.clone()
61-
if bias_correction == 1:
62-
m_corr = flag_gems.true_divide(m_corr, bias_correction1)
63-
v_corr = flag_gems.true_divide(v_corr, bias_correction2)
77+
# Compute update
78+
denom = flag_gems.add(flag_gems.sqrt(v_corr), epsilon)
79+
update = flag_gems.true_divide(m_corr, denom)
6480

65-
update = flag_gems.true_divide(m_corr, flag_gems.add(flag_gems.sqrt(v_corr), eps))
81+
# Update parameter
82+
p.add_(update, alpha=-lr)
83+
else: # mode == 1, AdamW (decoupled weight decay)
84+
# Update moments with original gradient
85+
flag_gems.add_(flag_gems.mul_(m, beta1), g, alpha=1-beta1)
86+
flag_gems.add_(flag_gems.mul_(v, beta2), flag_gems.mul(g, g), alpha=1-beta2)
6687

67-
if is_adamw:
68-
p = flag_gems.mul_(p, 1 - lr * weight_decay)
69-
else:
70-
update = flag_gems.add_(update, p, alpha=weight_decay)
88+
# Bias correction
89+
m_corr = flag_gems.true_divide(m, bias_correction1)
90+
v_corr = flag_gems.true_divide(v, bias_correction2)
7191

72-
p = flag_gems.add_(p, update, alpha=-lr)
92+
# Compute update with weight decay added (matches CUDA exactly)
93+
denom = flag_gems.add(flag_gems.sqrt(v_corr), epsilon)
94+
update = flag_gems.add(flag_gems.true_divide(m_corr, denom), p_float, alpha=weight_decay)
95+
96+
# Update parameter
97+
p.add_(update, alpha=-lr)
7398

7499
if p_master is not None:
75100
flag_gems.copy_(p_master, p)
76101
out_dtype = p_master.dtype if out_dtype is None else out_dtype
77102
p.data = p.data.to(out_dtype)
78103

79-
80104
def multi_tensor_adam_param_remainder_fl(
81105
chunk_size: int,
82106
noop_flag: torch.Tensor,
83107
tensor_lists: List[List[torch.Tensor]],
84108
lr: float,
85109
beta1: float,
86110
beta2: float,
87-
eps: float,
111+
epsilon: float,
88112
step: int,
89113
mode: int,
90114
bias_correction: int,
91115
weight_decay: float,
92-
inv_scale: Optional[float] = 1.0,
93116
) -> None:
94117
"""
95118
Adam optimizer with parameter remainders for BF16 precision (FlagOS implementation).
96-
97-
This variant stores BF16 parameters + int16 remainders to reconstruct FP32 master weights.
98-
Used when you have BF16 params and need FP32 master params without storing full FP32 copies.
99-
100-
Args:
101-
chunk_size: Chunk size for processing (unused in this implementation)
102-
noop_flag: If non-zero, skip computation
103-
tensor_lists: [grads, params (bf16), exp_avgs (fp32), exp_avg_sqs (fp32), param_remainders (int16)]
104-
lr: Learning rate
105-
beta1: First moment decay rate
106-
beta2: Second moment decay rate
107-
eps: Epsilon for numerical stability
108-
step: Current optimization step
109-
mode: 0 = L2 regularization, 1 = AdamW (decoupled weight decay)
110-
bias_correction: Whether to apply bias correction (1 = yes, 0 = no)
111-
weight_decay: Weight decay coefficient
112-
inv_scale: Inverse gradient scale for mixed precision training
113119
"""
114120
if noop_flag.item() != 0:
115121
return
@@ -133,58 +139,78 @@ def multi_tensor_adam_param_remainder_fl(
133139

134140
for i in range(num_tensors):
135141
g = tensor_lists[0][i]
136-
p = tensor_lists[1][i] # BF16 parameter
142+
p = tensor_lists[1][i] # int16 parameter (high 16 bits of FP32)
137143
m = tensor_lists[2][i] # FP32 first moment
138144
v = tensor_lists[3][i] # FP32 second moment
139-
p_remainder = tensor_lists[4][i] # int16 remainder
145+
p_remainder = tensor_lists[4][i] # int16 remainder (low 16 bits of FP32)
140146

141147
if not g.is_contiguous():
142148
g = g.contiguous()
143149

144-
# Apply gradient unscaling if needed
145-
if inv_scale is not None and inv_scale != 1.0:
146-
g = flag_gems.mul(g, inv_scale)
150+
# Convert gradient to float
151+
g_float = g.float()
152+
153+
# Reconstruct FP32 master weight from int16 param + int16 remainder using bit manipulation
154+
# This matches the CUDA implementation exactly:
155+
# 1. If p_remainder < 0, decrement p (undo rounding)
156+
# 2. Combine high 16 bits (p) and low 16 bits (p_remainder) into FP32
157+
# Note: Use PyTorch native ops for bit manipulation (int16/int32 operations)
158+
159+
local_p = p.view(torch.int16).clone()
160+
local_p_rem = p_remainder.clone()
147161

148-
# Reconstruct FP32 master weight from BF16 param + int16 remainder
149-
# The remainder represents the lower 16 bits lost in BF16 conversion
150-
param_fp32 = p.float()
151-
param_master = flag_gems.add(param_fp32, flag_gems.mul(p_remainder.float(), 2.0 ** -16))
162+
# Undo rounding: if remainder < 0, decrement p
163+
local_p = torch.where(local_p_rem < 0, local_p - 1, local_p)
152164

153-
# Compute gradient with weight decay (if L2 mode)
154-
grad_with_decay = g.float()
155-
if not is_adamw: # L2 regularization mode
156-
grad_with_decay = flag_gems.add(grad_with_decay, flag_gems.mul(param_master, weight_decay))
165+
# Combine into FP32 using bit shift operations
166+
# local_p is high 16 bits, local_p_rem is low 16 bits
167+
high_bits = local_p.to(torch.int32) << 16
168+
low_bits = local_p_rem.to(torch.int32) & 0xFFFF # Mask off sign extension
169+
param_int32 = high_bits | low_bits
170+
param_master = param_int32.view(torch.float32)
157171

158-
# Update moments
159-
m = flag_gems.add_(flag_gems.mul_(m, beta1), grad_with_decay, alpha=1 - beta1)
160-
v = flag_gems.add_(flag_gems.mul_(v, beta2), flag_gems.mul_(flag_gems.mul_(grad_with_decay, grad_with_decay), 1 - beta2))
172+
# L2 mode: add weight decay to gradient before updating moments
173+
if not is_adamw and weight_decay != 0:
174+
g_float = flag_gems.add(g_float, param_master, alpha=weight_decay)
175+
176+
# Update first moment: m = beta1 * m + (1 - beta1) * g
177+
flag_gems.add_(flag_gems.mul_(m, beta1), g_float, alpha=1 - beta1)
178+
179+
# Update second moment: v = beta2 * v + (1 - beta2) * g^2
180+
flag_gems.add_(flag_gems.mul_(v, beta2), flag_gems.mul(g_float, g_float), alpha=1 - beta2)
161181

162182
# Apply bias correction
163-
m_corr = m.clone()
164-
v_corr = v.clone()
165-
if bias_correction == 1:
166-
m_corr = flag_gems.true_divide(m_corr, bias_correction1)
167-
v_corr = flag_gems.true_divide(v_corr, bias_correction2)
183+
m_corr = flag_gems.true_divide(m, bias_correction1)
184+
v_corr = flag_gems.true_divide(v, bias_correction2)
185+
186+
# Compute denominator: sqrt(v_corr) + epsilon
187+
denom = flag_gems.add(flag_gems.sqrt(v_corr), epsilon)
168188

169189
# Compute update
170-
update = flag_gems.true_divide(m_corr, flag_gems.add(flag_gems.sqrt(v_corr), eps))
190+
update = flag_gems.true_divide(m_corr, denom)
191+
192+
# AdamW mode: add decoupled weight decay to update
193+
if is_adamw and weight_decay != 0:
194+
update = flag_gems.add(update, param_master, alpha=weight_decay)
171195

172-
# Apply weight decay (if AdamW mode)
173-
if is_adamw:
174-
param_master = flag_gems.mul_(param_master, 1 - lr * weight_decay)
196+
# Update master weight: p = p - lr * update
197+
param_master = flag_gems.sub(param_master, flag_gems.mul(update, lr))
175198

176-
# Update master weight
177-
param_master = flag_gems.add_(param_master, update, alpha=-lr)
199+
# Split FP32 back into int16 param + int16 remainder using bit manipulation
200+
# This matches the CUDA implementation exactly:
201+
# 1. Extract high 16 bits as p
202+
# 2. Extract low 16 bits as p_remainder
203+
# 3. If p_remainder < 0, increment p (round up)
204+
# Note: Use PyTorch native ops for bit manipulation (int32 operations)
178205

179-
# Split back into BF16 param + int16 remainder
180-
# Convert to BF16 (this is the rounded version)
181-
param_bf16 = param_master.to(dtype=p.dtype)
206+
param_int32 = param_master.view(torch.int32)
207+
# Extract low 16 bits (remainder) and high 16 bits (param)
208+
new_p_rem = (param_int32 & 0xFFFF).to(torch.int16)
209+
new_p = ((param_int32 >> 16) & 0xFFFF).to(torch.int16)
182210

183-
# Compute remainder: difference between FP32 master and BF16 representation
184-
# Scale and quantize to int16 range
185-
remainder_fp32 = flag_gems.mul(flag_gems.sub(param_master, param_bf16.float()), 2.0 ** 16)
186-
remainder_int16 = flag_gems.clamp(torch.round(remainder_fp32), -32768, 32767).to(dtype=torch.int16)
211+
# Round up: if remainder < 0, increment p
212+
new_p = torch.where(new_p_rem < 0, new_p + 1, new_p)
187213

188214
# Write back
189-
flag_gems.copy_(p, param_bf16)
190-
flag_gems.copy_(p_remainder, remainder_int16)
215+
p.view(torch.int16).copy_(new_p)
216+
p_remainder.copy_(new_p_rem)

transformer_engine/plugin/core/backends/flagos/impl/multi_tensor.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ def multi_tensor_l2_norm_fl(
3434

3535
for tensor in tensors:
3636
norm_sq = flag_gems.sum(tensor.float() ** 2)
37+
# Check for inf/nan (matches CUDA behavior)
38+
if not torch.isfinite(norm_sq):
39+
noop_flag.fill_(1)
3740
total_norm_sq = total_norm_sq + norm_sq
3841
if per_tensor:
3942
per_tensor_norms.append(flag_gems.sqrt(norm_sq))
@@ -47,7 +50,6 @@ def multi_tensor_l2_norm_fl(
4750

4851
return total_norm, per_tensor_result
4952

50-
5153
def multi_tensor_scale_fl(
5254
_chunk_size: int,
5355
noop_flag: torch.Tensor,
@@ -58,4 +60,7 @@ def multi_tensor_scale_fl(
5860
return
5961

6062
for src, dst in zip(tensor_lists[0], tensor_lists[1]):
63+
# Check for inf/nan (matches CUDA behavior for AMP gradient scaling)
64+
if not torch.isfinite(src).all():
65+
noop_flag.fill_(1)
6166
flag_gems.copy_(dst, src * scale)

0 commit comments

Comments
 (0)