1use std::collections::HashMap;
4use trustformers_core::errors::Result;
5use trustformers_core::parallel::ModelParallelContext;
6use trustformers_core::tensor::Tensor;
7
8#[derive(Debug, Clone)]
10pub struct ZeROState {
11 pub step: usize,
13 pub optimizer_states: HashMap<String, HashMap<String, Tensor>>,
15 pub gradient_partitions: HashMap<String, GradientBuffer>,
17 pub parameter_partitions: HashMap<String, ParameterPartition>,
19 pub communication_buffers: HashMap<String, Tensor>,
21}
22
23impl Default for ZeROState {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl ZeROState {
30 pub fn new() -> Self {
31 Self {
32 step: 0,
33 optimizer_states: HashMap::new(),
34 gradient_partitions: HashMap::new(),
35 parameter_partitions: HashMap::new(),
36 communication_buffers: HashMap::new(),
37 }
38 }
39
40 pub fn zero_grad(&mut self) {
42 for buffer in self.gradient_partitions.values_mut() {
43 buffer.zero();
44 }
45 }
46
47 pub fn step(&mut self) {
49 self.step += 1;
50 }
51
52 pub fn memory_usage(&self) -> HashMap<String, usize> {
54 let mut stats = HashMap::new();
55
56 let mut optimizer_memory = 0;
58 for states in self.optimizer_states.values() {
59 for tensor in states.values() {
60 optimizer_memory += tensor.memory_usage();
61 }
62 }
63 stats.insert("optimizer_states".to_string(), optimizer_memory);
64
65 let mut gradient_memory = 0;
67 for buffer in self.gradient_partitions.values() {
68 gradient_memory += buffer.memory_usage();
69 }
70 stats.insert("gradient_partitions".to_string(), gradient_memory);
71
72 let mut parameter_memory = 0;
74 for partition in self.parameter_partitions.values() {
75 parameter_memory += partition.memory_usage();
76 }
77 stats.insert("parameter_partitions".to_string(), parameter_memory);
78
79 let mut comm_memory = 0;
81 for tensor in self.communication_buffers.values() {
82 comm_memory += tensor.memory_usage();
83 }
84 stats.insert("communication_buffers".to_string(), comm_memory);
85
86 stats
87 }
88}
89
90#[derive(Debug, Clone)]
92pub struct ParameterGroup {
93 pub name: String,
95 pub parameter_names: Vec<String>,
97 pub local_parameters: HashMap<String, Tensor>,
99 pub partition_info: PartitionInfo,
101}
102
103impl ParameterGroup {
104 pub fn new(name: String, parameter_names: Vec<String>) -> Self {
105 Self {
106 name,
107 parameter_names,
108 local_parameters: HashMap::new(),
109 partition_info: PartitionInfo::default(),
110 }
111 }
112
113 pub fn add_parameter(&mut self, name: String, tensor: Tensor) {
115 self.local_parameters.insert(name.clone(), tensor);
116 if !self.parameter_names.contains(&name) {
117 self.parameter_names.push(name);
118 }
119 }
120
121 pub fn memory_usage(&self) -> usize {
123 self.local_parameters.values().map(|t| t.memory_usage()).sum()
124 }
125}
126
127#[derive(Debug, Clone)]
129pub struct PartitionInfo {
130 pub rank: usize,
132 pub world_size: usize,
134 pub start_idx: usize,
136 pub end_idx: usize,
138 pub global_shape: Vec<usize>,
140 pub local_shape: Vec<usize>,
142}
143
144impl Default for PartitionInfo {
145 fn default() -> Self {
146 Self {
147 rank: 0,
148 world_size: 1,
149 start_idx: 0,
150 end_idx: 0,
151 global_shape: vec![],
152 local_shape: vec![],
153 }
154 }
155}
156
157#[derive(Debug, Clone)]
159pub struct ParameterPartition {
160 pub name: String,
162 pub local_shard: Tensor,
164 pub partition_info: PartitionInfo,
166 pub is_gathered: bool,
168 pub full_parameter: Option<Tensor>,
170}
171
172impl ParameterPartition {
173 pub fn new(name: String, local_shard: Tensor, partition_info: PartitionInfo) -> Self {
174 Self {
175 name,
176 local_shard,
177 partition_info,
178 is_gathered: false,
179 full_parameter: None,
180 }
181 }
182
183 pub fn memory_usage(&self) -> usize {
185 let mut usage = self.local_shard.memory_usage();
186 if let Some(full_param) = &self.full_parameter {
187 usage += full_param.memory_usage();
188 }
189 usage
190 }
191
192 pub fn gather(&mut self, mp_context: &ModelParallelContext) -> Result<()> {
194 if self.is_gathered {
195 return Ok(());
196 }
197
198 let full_param =
200 mp_context.all_gather(&trustformers_core::parallel::DistributedTensor::new(
201 self.local_shard.clone(),
202 self.partition_info.global_shape.clone(),
203 trustformers_core::parallel::TensorPartition {
204 split_dim: 0, start_idx: self.partition_info.start_idx,
206 end_idx: self.partition_info.end_idx,
207 num_partitions: self.partition_info.world_size,
208 partition_rank: self.partition_info.rank,
209 },
210 self.partition_info.rank,
211 ))?;
212
213 self.full_parameter = Some(full_param);
214 self.is_gathered = true;
215 Ok(())
216 }
217
218 pub fn release(&mut self) {
220 self.full_parameter = None;
221 self.is_gathered = false;
222 }
223}
224
225#[derive(Debug, Clone)]
227pub struct GradientBuffer {
228 pub name: String,
230 pub local_gradient: Tensor,
232 pub accumulated_gradient: Option<Tensor>,
234 pub accumulation_steps: usize,
236 pub partition_info: PartitionInfo,
238}
239
240impl GradientBuffer {
241 pub fn new(name: String, local_gradient: Tensor, partition_info: PartitionInfo) -> Self {
242 Self {
243 name,
244 local_gradient,
245 accumulated_gradient: None,
246 accumulation_steps: 0,
247 partition_info,
248 }
249 }
250
251 pub fn zero(&mut self) {
253 if let Ok(zeros) = Tensor::zeros(&self.local_gradient.shape()) {
257 self.local_gradient = zeros;
258 }
259 self.accumulated_gradient = None;
260 self.accumulation_steps = 0;
261 }
262
263 pub fn accumulate(&mut self, gradient: &Tensor) -> Result<()> {
265 if let Some(acc_grad) = &mut self.accumulated_gradient {
266 *acc_grad = acc_grad.add(gradient)?;
267 } else {
268 self.accumulated_gradient = Some(gradient.clone());
269 }
270 self.accumulation_steps += 1;
271 Ok(())
272 }
273
274 pub fn get_accumulated(&self) -> Option<Tensor> {
276 if let Some(acc_grad) = &self.accumulated_gradient {
277 if self.accumulation_steps > 1 {
278 acc_grad.scalar_div(self.accumulation_steps as f32).ok()
279 } else {
280 Some(acc_grad.clone())
281 }
282 } else {
283 None
284 }
285 }
286
287 pub fn memory_usage(&self) -> usize {
289 let mut usage = self.local_gradient.memory_usage();
290 if let Some(acc_grad) = &self.accumulated_gradient {
291 usage += acc_grad.memory_usage();
292 }
293 usage
294 }
295}
296
297pub fn partition_parameters(
299 parameters: &HashMap<String, Tensor>,
300 world_size: usize,
301 rank: usize,
302) -> Result<HashMap<String, ParameterPartition>> {
303 let mut partitions = HashMap::new();
304
305 for (name, param) in parameters {
306 let shape = param.shape();
307 let total_elements = shape.iter().product::<usize>();
308
309 let elements_per_rank = total_elements.div_ceil(world_size);
311 let start_idx = rank * elements_per_rank;
312 let end_idx = ((rank + 1) * elements_per_rank).min(total_elements);
313
314 let local_shard = if world_size == 1 || total_elements <= elements_per_rank {
318 param.clone()
320 } else {
321 let scale_factor = 1.0 / (world_size as f32);
324
325 param.mul_scalar(scale_factor)?
326 };
327
328 let partition_info = PartitionInfo {
329 rank,
330 world_size,
331 start_idx,
332 end_idx,
333 global_shape: shape.to_vec(),
334 local_shape: local_shard.shape().to_vec(),
335 };
336
337 let partition = ParameterPartition::new(name.clone(), local_shard, partition_info);
338 partitions.insert(name.clone(), partition);
339 }
340
341 Ok(partitions)
342}
343
344pub fn gather_parameters(
346 partitions: &mut HashMap<String, ParameterPartition>,
347 mp_context: &ModelParallelContext,
348) -> Result<HashMap<String, Tensor>> {
349 let mut gathered = HashMap::new();
350
351 for (name, partition) in partitions.iter_mut() {
352 partition.gather(mp_context)?;
353 if let Some(full_param) = &partition.full_parameter {
354 gathered.insert(name.clone(), full_param.clone());
355 }
356 }
357
358 Ok(gathered)
359}
360
361pub fn partition_gradients(
363 gradients: &HashMap<String, Tensor>,
364 world_size: usize,
365 rank: usize,
366) -> Result<HashMap<String, GradientBuffer>> {
367 let mut buffers = HashMap::new();
368
369 for (name, grad) in gradients {
370 let shape = grad.shape();
371 let total_elements = shape.iter().product::<usize>();
372
373 let elements_per_rank = total_elements.div_ceil(world_size);
375 let start_idx = rank * elements_per_rank;
376 let end_idx = ((rank + 1) * elements_per_rank).min(total_elements);
377
378 let local_gradient = if world_size == 1 || total_elements <= elements_per_rank {
381 grad.clone()
383 } else {
384 let scale_factor = 1.0 / (world_size as f32);
386
387 grad.mul_scalar(scale_factor)?
388 };
389
390 let partition_info = PartitionInfo {
391 rank,
392 world_size,
393 start_idx,
394 end_idx,
395 global_shape: shape.to_vec(),
396 local_shape: local_gradient.shape().to_vec(),
397 };
398
399 let buffer = GradientBuffer::new(name.clone(), local_gradient, partition_info);
400 buffers.insert(name.clone(), buffer);
401 }
402
403 Ok(buffers)
404}
405
406pub fn all_gather_gradients(
408 buffers: &HashMap<String, GradientBuffer>,
409 mp_context: &ModelParallelContext,
410) -> Result<HashMap<String, Tensor>> {
411 let mut gathered = HashMap::new();
412
413 for (name, buffer) in buffers {
414 let distributed_tensor = trustformers_core::parallel::DistributedTensor::new(
415 buffer.local_gradient.clone(),
416 buffer.partition_info.global_shape.clone(),
417 trustformers_core::parallel::TensorPartition {
418 split_dim: 0,
419 start_idx: buffer.partition_info.start_idx,
420 end_idx: buffer.partition_info.end_idx,
421 num_partitions: buffer.partition_info.world_size,
422 partition_rank: buffer.partition_info.rank,
423 },
424 buffer.partition_info.rank,
425 );
426
427 let full_gradient = mp_context.all_gather(&distributed_tensor)?;
428 gathered.insert(name.clone(), full_gradient);
429 }
430
431 Ok(gathered)
432}
433
434pub fn reduce_scatter_gradients(
436 gradients: &HashMap<String, Tensor>,
437 mp_context: &ModelParallelContext,
438) -> Result<HashMap<String, Tensor>> {
439 let mut scattered = HashMap::new();
440
441 for (name, grad) in gradients {
442 let scattered_grad = mp_context.reduce_scatter(grad, 0)?;
443 scattered.insert(name.clone(), scattered_grad);
444 }
445
446 Ok(scattered)
447}
448
449pub fn calculate_bucket_size(
451 parameter_sizes: &[usize],
452 target_bucket_size: usize,
453) -> Vec<Vec<usize>> {
454 let mut buckets = Vec::new();
455 let mut current_bucket = Vec::new();
456 let mut current_size = 0;
457
458 for (i, &size) in parameter_sizes.iter().enumerate() {
459 if current_size + size > target_bucket_size && !current_bucket.is_empty() {
460 buckets.push(current_bucket);
461 current_bucket = Vec::new();
462 current_size = 0;
463 }
464
465 current_bucket.push(i);
466 current_size += size;
467 }
468
469 if !current_bucket.is_empty() {
470 buckets.push(current_bucket);
471 }
472
473 buckets
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479
480 #[test]
481 fn test_zero_state_creation() {
482 let state = ZeROState::new();
483 assert_eq!(state.step, 0);
484 assert!(state.optimizer_states.is_empty());
485 assert!(state.gradient_partitions.is_empty());
486 assert!(state.parameter_partitions.is_empty());
487 }
488
489 #[test]
490 fn test_parameter_group() {
491 let mut group = ParameterGroup::new("test_group".to_string(), vec!["param1".to_string()]);
492 let tensor = Tensor::ones(&[2, 2]).expect("Failed to create tensor");
493 group.add_parameter("param1".to_string(), tensor);
494
495 assert_eq!(group.parameter_names.len(), 1);
496 assert_eq!(group.local_parameters.len(), 1);
497 assert!(group.memory_usage() > 0);
498 }
499
500 #[test]
501 fn test_gradient_buffer() {
502 let tensor = Tensor::ones(&[2, 2]).expect("Failed to create tensor");
503 let partition_info = PartitionInfo::default();
504 let mut buffer = GradientBuffer::new("test_grad".to_string(), tensor, partition_info);
505
506 let grad = Tensor::ones(&[2, 2]).expect("Failed to create tensor");
507 buffer.accumulate(&grad).expect("Operation failed in test");
508
509 assert_eq!(buffer.accumulation_steps, 1);
510 assert!(buffer.get_accumulated().is_some());
511 }
512
513 #[test]
514 fn test_partition_parameters() {
515 let mut params = HashMap::new();
516 params.insert(
517 "param1".to_string(),
518 Tensor::ones(&[4, 4]).expect("Failed to create tensor"),
519 );
520 params.insert(
521 "param2".to_string(),
522 Tensor::ones(&[2, 2]).expect("Failed to create tensor"),
523 );
524
525 let partitions = partition_parameters(¶ms, 2, 0).expect("Operation failed in test");
526 assert_eq!(partitions.len(), 2);
527
528 for partition in partitions.values() {
529 assert_eq!(partition.partition_info.world_size, 2);
530 assert_eq!(partition.partition_info.rank, 0);
531 }
532 }
533
534 #[test]
535 fn test_calculate_bucket_size() {
536 let sizes = vec![100, 200, 150, 300, 50];
537 let buckets = calculate_bucket_size(&sizes, 400);
538
539 assert!(!buckets.is_empty());
540
541 for bucket in &buckets {
543 let bucket_size: usize = bucket.iter().map(|&i| sizes[i]).sum();
544 assert!(bucket_size <= 400 || bucket.len() == 1); }
546 }
547}