22#
33# See LICENSE for license information.
44
5- from typing import Optional , List
5+ from typing import List
66import torch
77import flag_gems
88
99
10+
11+
1012def multi_tensor_adam_fl (
1113 chunk_size : int ,
1214 noop_flag : torch .Tensor ,
@@ -19,8 +21,6 @@ def multi_tensor_adam_fl(
1921 mode : int ,
2022 bias_correction : int ,
2123 weight_decay : float ,
22- inv_scale : Optional [float ] = 1.0 ,
23- out_dtype : Optional [torch .dtype ] = None ,
2424) -> None :
2525
2626 num_lists = len (tensor_lists )
@@ -50,9 +50,6 @@ def multi_tensor_adam_fl(
5050 if not g .is_contiguous ():
5151 g = g .contiguous ()
5252
53- if inv_scale is not None and inv_scale != 1.0 :
54- g = flag_gems .mul (g , inv_scale )
55-
5653 m = flag_gems .add_ (flag_gems .mul_ (m , beta1 ), g , alpha = 1 - beta1 )
5754 v = flag_gems .add_ (flag_gems .mul_ (v , beta2 ), flag_gems .mul_ (flag_gems .mul_ (g , g ), 1 - beta2 ))
5855
@@ -73,9 +70,6 @@ def multi_tensor_adam_fl(
7370
7471 if p_master is not None :
7572 flag_gems .copy_ (p_master , p )
76- out_dtype = p_master .dtype if out_dtype is None else out_dtype
77- p .data = p .data .to (out_dtype )
78-
7973
8074def multi_tensor_adam_param_remainder_fl (
8175 chunk_size : int ,
@@ -89,27 +83,9 @@ def multi_tensor_adam_param_remainder_fl(
8983 mode : int ,
9084 bias_correction : int ,
9185 weight_decay : float ,
92- inv_scale : Optional [float ] = 1.0 ,
9386) -> None :
9487 """
9588 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
11389 """
11490 if noop_flag .item () != 0 :
11591 return
@@ -133,58 +109,78 @@ def multi_tensor_adam_param_remainder_fl(
133109
134110 for i in range (num_tensors ):
135111 g = tensor_lists [0 ][i ]
136- p = tensor_lists [1 ][i ] # BF16 parameter
112+ p = tensor_lists [1 ][i ] # int16 parameter (high 16 bits of FP32)
137113 m = tensor_lists [2 ][i ] # FP32 first moment
138114 v = tensor_lists [3 ][i ] # FP32 second moment
139- p_remainder = tensor_lists [4 ][i ] # int16 remainder
115+ p_remainder = tensor_lists [4 ][i ] # int16 remainder (low 16 bits of FP32)
140116
141117 if not g .is_contiguous ():
142118 g = g .contiguous ()
143119
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 )
120+ # Convert gradient to float
121+ g_float = g .float ()
122+
123+ # Reconstruct FP32 master weight from int16 param + int16 remainder using bit manipulation
124+ # This matches the CUDA implementation exactly:
125+ # 1. If p_remainder < 0, decrement p (undo rounding)
126+ # 2. Combine high 16 bits (p) and low 16 bits (p_remainder) into FP32
127+ # Note: Use PyTorch native ops for bit manipulation (int16/int32 operations)
128+
129+ local_p = p .view (torch .int16 ).clone ()
130+ local_p_rem = p_remainder .clone ()
131+
132+ # Undo rounding: if remainder < 0, decrement p
133+ local_p = torch .where (local_p_rem < 0 , local_p - 1 , local_p )
134+
135+ # Combine into FP32 using bit shift operations
136+ # local_p is high 16 bits, local_p_rem is low 16 bits
137+ high_bits = local_p .to (torch .int32 ) << 16
138+ low_bits = local_p_rem .to (torch .int32 ) & 0xFFFF # Mask off sign extension
139+ param_int32 = high_bits | low_bits
140+ param_master = param_int32 .view (torch .float32 )
147141
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 ))
142+ # L2 mode: add weight decay to gradient before updating moments
143+ if not is_adamw and weight_decay != 0 :
144+ g_float = flag_gems .add (g_float , param_master , alpha = weight_decay )
152145
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 ))
146+ # Update first moment: m = beta1 * m + (1 - beta1) * g
147+ flag_gems .add_ (flag_gems .mul_ (m , beta1 ), g_float , alpha = 1 - beta1 )
157148
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 ))
149+ # Update second moment: v = beta2 * v + (1 - beta2) * g^2
150+ flag_gems .add_ (flag_gems .mul_ (v , beta2 ), flag_gems .mul (g_float , g_float ), alpha = 1 - beta2 )
161151
162152 # 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 )
153+ m_corr = flag_gems . true_divide ( m , bias_correction1 )
154+ v_corr = flag_gems . true_divide ( v , bias_correction2 )
155+
156+ # Compute denominator: sqrt(v_corr) + eps
157+ denom = flag_gems .add ( flag_gems . sqrt ( v_corr ), eps )
168158
169159 # Compute update
170- update = flag_gems .true_divide (m_corr , flag_gems . add ( flag_gems . sqrt ( v_corr ), eps ) )
160+ update = flag_gems .true_divide (m_corr , denom )
171161
172- # Apply weight decay (if AdamW mode)
173- if is_adamw :
174- param_master = flag_gems .mul_ (param_master , 1 - lr * weight_decay )
162+ # AdamW mode: add decoupled weight decay to update
163+ if is_adamw and weight_decay != 0 :
164+ update = flag_gems .add (update , param_master , alpha = weight_decay )
165+
166+ # Update master weight: p = p - lr * update
167+ param_master = flag_gems .sub (param_master , flag_gems .mul (update , lr ))
175168
176- # Update master weight
177- param_master = flag_gems .add_ (param_master , update , alpha = - lr )
169+ # Split FP32 back into int16 param + int16 remainder using bit manipulation
170+ # This matches the CUDA implementation exactly:
171+ # 1. Extract high 16 bits as p
172+ # 2. Extract low 16 bits as p_remainder
173+ # 3. If p_remainder < 0, increment p (round up)
174+ # Note: Use PyTorch native ops for bit manipulation (int32 operations)
178175
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 )
176+ param_int32 = param_master .view (torch .int32 )
177+ # Extract low 16 bits (remainder) and high 16 bits (param)
178+ new_p_rem = (param_int32 & 0xFFFF ).to (torch .int16 )
179+ new_p = ((param_int32 >> 16 ) & 0xFFFF ).to (torch .int16 )
182180
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 )
181+ # Round up: if remainder < 0, increment p
182+ new_p = torch .where (new_p_rem < 0 , new_p + 1 , new_p )
187183
188184 # Write back
189- flag_gems .copy_ (p , param_bf16 )
190- flag_gems .copy_ (p_remainder , remainder_int16 )
185+ flag_gems .copy_ (p , new_p . view ( torch . bfloat16 ) )
186+ flag_gems .copy_ (p_remainder , new_p_rem )
0 commit comments