1414from .storage .grouped_tensor_storage import GroupedTensorStorage
1515
1616
17- # For now, conservatively ban all shape manipulating ops.
17+ def _stride_from_shape (shape : Tuple [int , ...]) -> Tuple [int , ...]:
18+ """Calculate contiguous stride from shape."""
19+ if len (shape ) == 0 :
20+ return ()
21+ stride = [1 ] * len (shape )
22+ for i in range (len (shape ) - 2 , - 1 , - 1 ):
23+ stride [i ] = stride [i + 1 ] * shape [i + 1 ]
24+ return tuple (stride )
25+
26+
27+ class _GroupedIdentityFunc (torch .autograd .Function ):
28+ """Identity autograd function used to create a dummy grad_fn node."""
29+
30+ @staticmethod
31+ def forward (ctx , tensor : "GroupedTensor" ) -> "GroupedTensor" :
32+ # pylint: disable=missing-function-docstring
33+ ctx .input_dtype = tensor .dtype
34+ return tensor .detach ()
35+
36+ @staticmethod
37+ def backward (ctx , grad_output : torch .Tensor ):
38+ # pylint: disable=missing-function-docstring
39+ grad_input = grad_output
40+ if grad_input .dtype != ctx .input_dtype :
41+ grad_input = grad_input .to (ctx .input_dtype )
42+ return grad_input
43+
44+
45+ # For now, conservatively ban 'most' shape manipulating ops.
1846BANNED_SHAPE_OPS = {
19- torch .ops .aten .view .default ,
20- torch .ops .aten ._unsafe_view .default ,
2147 torch .ops .aten .reshape .default ,
2248 torch .ops .aten ._reshape_alias .default ,
2349 torch .ops .aten .flatten .using_ints ,
3460 torch .ops .aten .select .int ,
3561 torch .ops .aten .split .Tensor ,
3662 torch .ops .aten .chunk .default ,
37- torch .ops .aten .expand .default ,
38- torch .ops .aten .expand_as .default ,
3963 torch .ops .aten .cat .default ,
4064 torch .ops .aten .stack .default ,
4165}
@@ -48,6 +72,7 @@ def __new__(
4872 cls ,
4973 shape : Tuple [int , int ],
5074 dtype : torch .dtype ,
75+ * ,
5176 num_tensors : int ,
5277 shapes : Optional [List [Tuple [int , int ]]] = None ,
5378 quantizer : Optional [Quantizer ] = None ,
@@ -64,12 +89,9 @@ def __new__(
6489 offsets : Optional [List [int ]] = None ,
6590 scale_inv_offsets : Optional [List [int ]] = None ,
6691 columnwise_scale_inv_offsets : Optional [List [int ]] = None ,
92+ requires_grad : bool = False ,
93+ stride : Optional [List [int ]] = None ,
6794 ):
68- del quantizer
69- del offsets
70- del scale_inv_offsets
71- del columnwise_scale_inv_offsets
72-
7395 if (
7496 shapes is not None
7597 and len (shapes ) == num_tensors
@@ -99,29 +121,136 @@ def __new__(
99121 if device is None :
100122 device = torch .device ("cuda" )
101123
102- strides = [ 1 ] * len ( wrapper_shape )
103- for i in range ( len ( wrapper_shape ) - 2 , - 1 , - 1 ):
104- strides [ i ] = strides [ i + 1 ] * wrapper_shape [ i + 1 ]
105- return torch .Tensor ._make_wrapper_subclass (
124+ # Match QuantizedTensor __new__: accept externally-computed stride to
125+ # avoid Python-side stride computation overhead for C++ construction.
126+ strides = _stride_from_shape ( tuple ( wrapper_shape )) if stride is None else tuple ( stride )
127+ instance = torch .Tensor ._make_wrapper_subclass (
106128 cls ,
107129 wrapper_shape ,
108- strides = tuple ( strides ) ,
130+ strides = strides ,
109131 storage_offset = 0 ,
110132 dtype = dtype ,
111133 layout = torch .strided ,
112- requires_grad = False ,
134+ requires_grad = requires_grad ,
113135 device = device ,
114136 )
137+ GroupedTensorStorage ._initialize_storage_fields (
138+ instance = instance ,
139+ shape = shape ,
140+ dtype = dtype ,
141+ num_tensors = num_tensors ,
142+ shapes = shapes ,
143+ quantizer = quantizer ,
144+ data = data ,
145+ columnwise_data = columnwise_data ,
146+ scale_inv = scale_inv ,
147+ columnwise_scale_inv = columnwise_scale_inv ,
148+ amax = amax ,
149+ columnwise_amax = columnwise_amax ,
150+ scale = scale ,
151+ first_dims = first_dims ,
152+ last_dims = last_dims ,
153+ tensor_offsets = tensor_offsets ,
154+ offsets = offsets ,
155+ scale_inv_offsets = scale_inv_offsets ,
156+ columnwise_scale_inv_offsets = columnwise_scale_inv_offsets ,
157+ )
158+ return instance
115159
116160 @classmethod
117161 def __torch_dispatch__ (cls , func , types , args , kwargs = None ):
118162 """Dispatch by dequantizing grouped members, then requantizing writes."""
119163 if kwargs is None :
120164 kwargs = {}
121165
166+ def copy_grouped_storage_metadata (dst : GroupedTensor , src : GroupedTensor ) -> None :
167+ """Shallow-copy grouped-storage metadata onto wrapper outputs."""
168+ dst .num_tensors = src .num_tensors
169+ dst .quantizer = src .quantizer
170+ dst .tensor_shapes = src .tensor_shapes
171+ dst .fake_dtype = src .fake_dtype
172+ dst .rowwise_data = src .rowwise_data
173+ dst .columnwise_data = src .columnwise_data
174+ dst .scale_inv = src .scale_inv
175+ dst .columnwise_scale_inv = src .columnwise_scale_inv
176+ dst .amax = src .amax
177+ dst .columnwise_amax = src .columnwise_amax
178+ dst .scale = src .scale
179+ dst .first_dims = src .first_dims
180+ dst .last_dims = src .last_dims
181+ dst .tensor_offsets = src .tensor_offsets
182+ dst .offsets = src .offsets
183+ dst .scale_inv_offsets = src .scale_inv_offsets
184+ dst .columnwise_scale_inv_offsets = src .columnwise_scale_inv_offsets
185+ dst .logical_shape = src .logical_shape
186+ dst .quantized_tensors = src .quantized_tensors
187+
188+ def make_wrapper_like (src : GroupedTensor , requires_grad : bool ) -> GroupedTensor :
189+ """Create a wrapper of the same type and tensor metadata as src."""
190+ out = torch .Tensor ._make_wrapper_subclass (
191+ type (src ),
192+ tuple (src .shape ),
193+ strides = tuple (src .stride ()),
194+ storage_offset = src .storage_offset (),
195+ dtype = src .dtype ,
196+ layout = src .layout ,
197+ requires_grad = requires_grad ,
198+ device = src .device ,
199+ )
200+ copy_grouped_storage_metadata (out , src )
201+ return out
202+
122203 # Parameter construction calls detach()/alias-like paths.
123204 if func in (torch .ops .aten .detach .default , torch .ops .aten .alias .default ):
124- return args [0 ]
205+ src = args [0 ]
206+ assert isinstance (src , GroupedTensor )
207+ if func == torch .ops .aten .detach .default :
208+ return make_wrapper_like (src , requires_grad = False )
209+ return make_wrapper_like (src , requires_grad = src .requires_grad )
210+
211+ # Parameter construction may invoke aten.expand on tensor subclasses.
212+ # Handle this explicitly so grouped parameters can be created safely.
213+ if func == torch .ops .aten .expand .default :
214+ src = args [0 ]
215+ assert isinstance (src , GroupedTensor )
216+ expanded_shape = tuple (args [1 ])
217+ src_shape = tuple (src .shape )
218+ if len (expanded_shape ) == len (src_shape ):
219+ normalized_shape = tuple (
220+ src_shape [i ] if dim == - 1 else dim for i , dim in enumerate (expanded_shape )
221+ )
222+ if normalized_shape == src_shape :
223+ return make_wrapper_like (src , requires_grad = src .requires_grad )
224+ return super ().__torch_dispatch__ (func , types , args , kwargs )
225+
226+ # DDP and mcore use expand_as(self) to build a dummy autograd node and
227+ # access gradient accumulators during parameter hook registration.
228+ if func == torch .ops .aten .expand_as .default :
229+ src = args [0 ]
230+ other = args [1 ]
231+ assert isinstance (src , GroupedTensor )
232+ if other is src :
233+ return _GroupedIdentityFunc .apply (src )
234+ if tuple (other .shape ) == tuple (src .shape ):
235+ return make_wrapper_like (src , requires_grad = src .requires_grad )
236+ return super ().__torch_dispatch__ (func , types , args , kwargs )
237+
238+ # Distributed optimizer flattens detached parameters via
239+ # model_param.detach().view(-1). Support this path explicitly by
240+ # returning a flat view of grouped backing storage.
241+ if func in (torch .ops .aten .view .default , torch .ops .aten ._unsafe_view .default ):
242+ src = args [0 ]
243+ assert isinstance (src , GroupedTensor )
244+ target_shape = tuple (args [1 ])
245+ if target_shape in ((- 1 ,), (src .numel (),)):
246+ if src .rowwise_data is not None :
247+ return src .rowwise_data .view (- 1 )
248+ raise RuntimeError (
249+ f"{ cls .__name__ } view(-1) requires rowwise_data to be initialized"
250+ )
251+ raise RuntimeError (
252+ f"{ cls .__name__ } only supports view(-1) for distributed optimizer flattening"
253+ )
125254
126255 # Don't allow reshape/view etc.
127256 if func in BANNED_SHAPE_OPS :
@@ -203,3 +332,10 @@ def __torch_function__(cls, func, types, args=(), kwargs=None):
203332 kwargs = {}
204333 # Do not force GroupedTensor on outputs.
205334 return torch ._C ._disabled_torch_function_impl (func , types , args , kwargs )
335+
336+ def expand_as (self , other : torch .Tensor ) -> torch .Tensor :
337+ # pylint: disable=missing-function-docstring
338+ # Needed during parameter creation/hook registration paths.
339+ if other is self :
340+ return _GroupedIdentityFunc .apply (self )
341+ return super ().expand_as (other )
0 commit comments