@@ -14,20 +14,15 @@ def multi_tensor_adam_fl(
1414 lr : float ,
1515 beta1 : float ,
1616 beta2 : float ,
17- epsilon : float ,
17+ eps : 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.
2725
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- """
3126 num_lists = len (tensor_lists )
3227 assert num_lists in [4 , 5 ], f"Expected 4 or 5 tensor lists, got { num_lists } "
3328
@@ -55,67 +50,66 @@ def multi_tensor_adam_fl(
5550 if not g .is_contiguous ():
5651 g = g .contiguous ()
5752
58- # Convert to float for computation (matches CUDA's MATH_T = float)
59- g = g .float ()
60- p_float = p .float ()
61-
6253 if inv_scale is not None and inv_scale != 1.0 :
6354 g = flag_gems .mul (g , inv_scale )
6455
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 )
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 ))
7658
77- # Compute update
78- denom = flag_gems .add (flag_gems .sqrt (v_corr ), epsilon )
79- update = flag_gems .true_divide (m_corr , denom )
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 )
8064
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 )
65+ update = flag_gems .true_divide (m_corr , flag_gems .add (flag_gems .sqrt (v_corr ), eps ))
8766
88- # Bias correction
89- m_corr = flag_gems .true_divide (m , bias_correction1 )
90- v_corr = flag_gems .true_divide (v , bias_correction2 )
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 )
9171
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 )
72+ p = flag_gems .add_ (p , update , alpha = - lr )
9873
9974 if p_master is not None :
10075 flag_gems .copy_ (p_master , p )
10176 out_dtype = p_master .dtype if out_dtype is None else out_dtype
10277 p .data = p .data .to (out_dtype )
10378
79+
10480def multi_tensor_adam_param_remainder_fl (
10581 chunk_size : int ,
10682 noop_flag : torch .Tensor ,
10783 tensor_lists : List [List [torch .Tensor ]],
10884 lr : float ,
10985 beta1 : float ,
11086 beta2 : float ,
111- epsilon : float ,
87+ eps : float ,
11288 step : int ,
11389 mode : int ,
11490 bias_correction : int ,
11591 weight_decay : float ,
92+ inv_scale : Optional [float ] = 1.0 ,
11693) -> None :
11794 """
11895 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
119113 """
120114 if noop_flag .item () != 0 :
121115 return
@@ -139,78 +133,58 @@ def multi_tensor_adam_param_remainder_fl(
139133
140134 for i in range (num_tensors ):
141135 g = tensor_lists [0 ][i ]
142- p = tensor_lists [1 ][i ] # int16 parameter (high 16 bits of FP32)
136+ p = tensor_lists [1 ][i ] # BF16 parameter
143137 m = tensor_lists [2 ][i ] # FP32 first moment
144138 v = tensor_lists [3 ][i ] # FP32 second moment
145- p_remainder = tensor_lists [4 ][i ] # int16 remainder (low 16 bits of FP32)
139+ p_remainder = tensor_lists [4 ][i ] # int16 remainder
146140
147141 if not g .is_contiguous ():
148142 g = g .contiguous ()
149143
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 ()
161-
162- # Undo rounding: if remainder < 0, decrement p
163- local_p = torch .where (local_p_rem < 0 , local_p - 1 , local_p )
164-
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 )
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 )
171147
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 )
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 ))
175152
176- # Update first moment: m = beta1 * m + (1 - beta1) * g
177- flag_gems .add_ (flag_gems .mul_ (m , beta1 ), g_float , alpha = 1 - beta1 )
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 ))
178157
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 )
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 ))
181161
182162 # Apply bias correction
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 )
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 )
188168
189169 # Compute update
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 )
170+ update = flag_gems .true_divide (m_corr , flag_gems .add (flag_gems .sqrt (v_corr ), eps ))
195171
196- # Update master weight: p = p - lr * update
197- param_master = flag_gems .sub (param_master , flag_gems .mul (update , lr ))
172+ # Apply weight decay (if AdamW mode)
173+ if is_adamw :
174+ param_master = flag_gems .mul_ (param_master , 1 - lr * weight_decay )
198175
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)
176+ # Update master weight
177+ param_master = flag_gems .add_ (param_master , update , alpha = - lr )
205178
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 )
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 )
210182
211- # Round up: if remainder < 0, increment p
212- new_p = torch .where (new_p_rem < 0 , new_p + 1 , new_p )
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 )
213187
214188 # Write back
215- flag_gems .copy_ (p , new_p . view ( torch . bfloat16 ) )
216- flag_gems .copy_ (p_remainder , new_p_rem )
189+ flag_gems .copy_ (p , param_bf16 )
190+ flag_gems .copy_ (p_remainder , remainder_int16 )
0 commit comments