1use std::{
8 collections::{BTreeMap, BTreeSet},
9 ops::Range,
10};
11
12use eredu_checkpoint::LinearFormat;
13use eredu_nn::{LinearFormatSpec, ParameterMetadata, ParameterVisitor, Parameterized, Tensor};
14
15#[derive(Debug, Clone)]
17pub struct ParallelModelInfo<T> {
18 topology: T,
19 effective_model_type: String,
20 owned_tensors: Vec<String>,
21 local_parameter_bytes: u64,
22 global_parameter_bytes: u64,
23 pinned_device_parameter_bytes: u64,
24 maximum_device_parameter_bytes: u64,
25}
26
27impl<T> ParallelModelInfo<T> {
28 #[allow(clippy::too_many_arguments)]
30 pub fn new(
31 topology: T,
32 effective_model_type: impl Into<String>,
33 owned_tensors: Vec<String>,
34 local_parameter_bytes: u64,
35 global_parameter_bytes: u64,
36 pinned_device_parameter_bytes: u64,
37 maximum_device_parameter_bytes: u64,
38 ) -> Self {
39 Self {
40 topology,
41 effective_model_type: effective_model_type.into(),
42 owned_tensors,
43 local_parameter_bytes,
44 global_parameter_bytes,
45 pinned_device_parameter_bytes,
46 maximum_device_parameter_bytes,
47 }
48 }
49
50 pub fn topology(&self) -> T
52 where
53 T: Clone,
54 {
55 self.topology.clone()
56 }
57
58 pub fn effective_model_type(&self) -> &str {
60 &self.effective_model_type
61 }
62
63 pub fn owned_tensors(&self) -> &[String] {
65 &self.owned_tensors
66 }
67
68 pub const fn local_parameter_bytes(&self) -> u64 {
70 self.local_parameter_bytes
71 }
72
73 pub const fn global_parameter_bytes(&self) -> u64 {
75 self.global_parameter_bytes
76 }
77
78 pub const fn pinned_device_parameter_bytes(&self) -> u64 {
80 self.pinned_device_parameter_bytes
81 }
82
83 pub const fn maximum_device_parameter_bytes(&self) -> u64 {
85 self.maximum_device_parameter_bytes
86 }
87}
88
89#[derive(Debug, Clone, Copy, Eq, PartialEq)]
91pub enum ParameterRole {
92 Replicated,
94 ColumnProjection,
96 RowProjection,
98 Vocabulary,
100 AttentionHeads,
102 FeedForwardIntermediate,
104 ExpertIntermediate,
106 Channels,
108 Segmented,
110}
111
112#[derive(Debug, Clone, Copy, Eq, PartialEq)]
114pub enum ProjectionSharding {
115 Replicated,
117 Column,
119 Row,
121}
122
123#[derive(Debug, Clone, Eq, PartialEq)]
125pub enum MemberSharding {
126 Replicated,
128 Equal {
130 axis: usize,
132 },
133 Balanced {
135 axis: usize,
137 },
138 Partitioned {
140 axis: usize,
142 },
143 PartitionedSegments {
145 axis: usize,
147 segments: Vec<Range<usize>>,
149 },
150 Segmented {
152 axis: usize,
154 segments: Vec<Range<usize>>,
156 },
157}
158
159#[derive(Debug, Clone, Eq, PartialEq)]
161pub struct ParameterMemberSpec {
162 target: String,
163 global_shape: Vec<usize>,
164 sharding: MemberSharding,
165}
166
167impl ParameterMemberSpec {
168 pub fn new(
170 target: impl Into<String>,
171 global_shape: impl Into<Vec<usize>>,
172 sharding: MemberSharding,
173 ) -> Self {
174 Self {
175 target: target.into(),
176 global_shape: global_shape.into(),
177 sharding,
178 }
179 }
180
181 pub fn target(&self) -> &str {
183 &self.target
184 }
185
186 pub fn global_shape(&self) -> &[usize] {
188 &self.global_shape
189 }
190
191 pub const fn sharding(&self) -> &MemberSharding {
193 &self.sharding
194 }
195}
196
197#[derive(Debug, Clone, Eq, PartialEq)]
199pub struct ParameterGroupSpec {
200 logical_name: String,
201 role: ParameterRole,
202 partition_units: Option<usize>,
203 members: Vec<ParameterMemberSpec>,
204}
205
206impl ParameterGroupSpec {
207 pub fn new(
209 logical_name: impl Into<String>,
210 role: ParameterRole,
211 members: impl IntoIterator<Item = ParameterMemberSpec>,
212 ) -> Result<Self, ParallelPlanError> {
213 Self::build(logical_name.into(), role, None, members)
214 }
215
216 pub fn partitioned(
218 logical_name: impl Into<String>,
219 role: ParameterRole,
220 units: usize,
221 members: impl IntoIterator<Item = ParameterMemberSpec>,
222 ) -> Result<Self, ParallelPlanError> {
223 if units == 0 {
224 return Err(ParallelPlanError::InvalidGroup(
225 "parallel logical partition must contain at least one unit".into(),
226 ));
227 }
228 Self::build(logical_name.into(), role, Some(units), members)
229 }
230
231 fn build(
232 logical_name: String,
233 role: ParameterRole,
234 partition_units: Option<usize>,
235 members: impl IntoIterator<Item = ParameterMemberSpec>,
236 ) -> Result<Self, ParallelPlanError> {
237 if logical_name.trim().is_empty() {
238 return Err(ParallelPlanError::InvalidGroup(
239 "parallel parameter logical name must not be empty".into(),
240 ));
241 }
242 let members = members.into_iter().collect::<Vec<_>>();
243 if members.is_empty() {
244 return Err(ParallelPlanError::InvalidGroup(format!(
245 "parallel parameter group {logical_name:?} must contain at least one tensor"
246 )));
247 }
248 let mut targets = BTreeSet::new();
249 let mut has_partitioned_member = false;
250 for member in &members {
251 if member.target.trim().is_empty() {
252 return Err(ParallelPlanError::InvalidGroup(format!(
253 "parallel parameter group {logical_name:?} contains an empty tensor target"
254 )));
255 }
256 if !targets.insert(member.target.clone()) {
257 return Err(ParallelPlanError::InvalidGroup(format!(
258 "parallel parameter group {logical_name:?} repeats tensor target {:?}",
259 member.target
260 )));
261 }
262 has_partitioned_member |= matches!(
263 member.sharding,
264 MemberSharding::Partitioned { .. } | MemberSharding::PartitionedSegments { .. }
265 );
266 }
267 if has_partitioned_member != partition_units.is_some() {
268 return Err(ParallelPlanError::InvalidGroup(format!(
269 "parallel parameter group {logical_name:?} must declare exactly one group-level logical partition for its partitioned members"
270 )));
271 }
272 Ok(Self {
273 logical_name,
274 role,
275 partition_units,
276 members,
277 })
278 }
279
280 pub fn logical_name(&self) -> &str {
282 &self.logical_name
283 }
284
285 pub const fn role(&self) -> ParameterRole {
287 self.role
288 }
289
290 pub const fn partition_units(&self) -> Option<usize> {
292 self.partition_units
293 }
294
295 pub fn members(&self) -> &[ParameterMemberSpec] {
297 &self.members
298 }
299}
300
301pub fn module_parameter_group<T, M>(
303 logical_name: impl Into<String>,
304 role: ParameterRole,
305 module: &M,
306 mut sharding: impl FnMut(&ParameterMetadata, &[usize]) -> Result<MemberSharding, ParallelPlanError>,
307) -> Result<ParameterGroupSpec, ParallelPlanError>
308where
309 T: Tensor,
310 M: Parameterized<T>,
311{
312 struct Collector<'a, F> {
313 members: Vec<ParameterMemberSpec>,
314 sharding: &'a mut F,
315 error: Option<ParallelPlanError>,
316 }
317
318 impl<'a, 'tensor, T, F> ParameterVisitor<'tensor, T> for Collector<'a, F>
319 where
320 T: Tensor,
321 F: FnMut(&ParameterMetadata, &[usize]) -> Result<MemberSharding, ParallelPlanError>,
322 {
323 fn visit(&mut self, metadata: ParameterMetadata, value: &'tensor T) {
324 if self.error.is_some() {
325 return;
326 }
327 let shape = value
328 .shape()
329 .iter()
330 .map(|dimension| {
331 usize::try_from(*dimension).map_err(|_| {
332 ParallelPlanError::InvalidTensor(format!(
333 "parameter {} has negative dimension {dimension}",
334 metadata.id.as_str()
335 ))
336 })
337 })
338 .collect::<Result<Vec<_>, _>>();
339 let shape = match shape {
340 Ok(shape) => shape,
341 Err(error) => {
342 self.error = Some(error);
343 return;
344 }
345 };
346 match (self.sharding)(&metadata, &shape) {
347 Ok(sharding) => self.members.push(ParameterMemberSpec::new(
348 metadata.id.as_str(),
349 shape,
350 sharding,
351 )),
352 Err(error) => self.error = Some(error),
353 }
354 }
355 }
356
357 let mut collector = Collector {
358 members: Vec::new(),
359 sharding: &mut sharding,
360 error: None,
361 };
362 module.visit_parameters(&mut collector);
363 if let Some(error) = collector.error {
364 return Err(error);
365 }
366 ParameterGroupSpec::new(logical_name, role, collector.members)
367}
368
369pub fn partitioned_module_parameter_group<T, M>(
371 logical_name: impl Into<String>,
372 role: ParameterRole,
373 preferred_units: usize,
374 module: &M,
375 mut sharding: impl FnMut(&ParameterMetadata, &[usize]) -> Result<MemberSharding, ParallelPlanError>,
376) -> Result<ParameterGroupSpec, ParallelPlanError>
377where
378 T: Tensor,
379 M: Parameterized<T>,
380{
381 if preferred_units == 0 {
382 return Err(ParallelPlanError::InvalidGroup(
383 "partitioned module group has zero preferred units".into(),
384 ));
385 }
386 struct Collector<'a, F> {
387 members: Vec<ParameterMemberSpec>,
388 sharding: &'a mut F,
389 error: Option<ParallelPlanError>,
390 }
391 impl<'a, 'tensor, T, F> ParameterVisitor<'tensor, T> for Collector<'a, F>
392 where
393 T: Tensor,
394 F: FnMut(&ParameterMetadata, &[usize]) -> Result<MemberSharding, ParallelPlanError>,
395 {
396 fn visit(&mut self, metadata: ParameterMetadata, value: &'tensor T) {
397 if self.error.is_some() {
398 return;
399 }
400 let shape = value
401 .shape()
402 .iter()
403 .map(|dimension| {
404 usize::try_from(*dimension).map_err(|_| {
405 ParallelPlanError::InvalidTensor(format!(
406 "parameter {} has negative dimension {dimension}",
407 metadata.id.as_str()
408 ))
409 })
410 })
411 .collect::<Result<Vec<_>, _>>();
412 match shape.and_then(|shape| {
413 (self.sharding)(&metadata, &shape)
414 .map(|sharding| ParameterMemberSpec::new(metadata.id.as_str(), shape, sharding))
415 }) {
416 Ok(member) => self.members.push(member),
417 Err(error) => self.error = Some(error),
418 }
419 }
420 }
421 let mut collector = Collector {
422 members: Vec::new(),
423 sharding: &mut sharding,
424 error: None,
425 };
426 module.visit_parameters(&mut collector);
427 if let Some(error) = collector.error {
428 return Err(error);
429 }
430 partitioned_group_with_preferred_units(logical_name, role, preferred_units, collector.members)
431}
432
433pub fn projection_parameter_group<T, M>(
435 logical_name: impl Into<String>,
436 role: ParameterRole,
437 module: &M,
438 placement: ProjectionSharding,
439) -> Result<ParameterGroupSpec, ParallelPlanError>
440where
441 T: Tensor,
442 M: Parameterized<T>,
443{
444 module_parameter_group(
445 logical_name,
446 role,
447 module,
448 |metadata, shape| match placement {
449 ProjectionSharding::Replicated => Ok(MemberSharding::Replicated),
450 ProjectionSharding::Column if shape.is_empty() => {
451 Err(ParallelPlanError::InvalidTensor(format!(
452 "column projection parameter {} is scalar",
453 metadata.id.as_str()
454 )))
455 }
456 ProjectionSharding::Column => Ok(MemberSharding::Equal { axis: 0 }),
457 ProjectionSharding::Row if shape.len() >= 2 => Ok(MemberSharding::Equal { axis: 1 }),
458 ProjectionSharding::Row => Ok(MemberSharding::Replicated),
459 },
460 )
461}
462
463pub fn partitioned_projection_group<T, M>(
465 logical_name: impl Into<String>,
466 role: ParameterRole,
467 projections: &[(&M, ProjectionSharding)],
468 preferred_units: usize,
469) -> Result<ParameterGroupSpec, ParallelPlanError>
470where
471 T: Tensor,
472 M: Parameterized<T>,
473{
474 if preferred_units == 0 {
475 return Err(ParallelPlanError::InvalidGroup(
476 "partitioned projection group has zero preferred units".into(),
477 ));
478 }
479 let mut members = Vec::new();
480 for (module, placement) in projections {
481 let group = projection_parameter_group::<T, M>("projection", role, *module, *placement)?;
482 for member in group.members {
483 let sharding = match (placement, member.global_shape.len()) {
484 (ProjectionSharding::Replicated, _) | (ProjectionSharding::Row, 0 | 1) => {
485 MemberSharding::Replicated
486 }
487 (ProjectionSharding::Column, 0) => unreachable!("validated above"),
488 (ProjectionSharding::Column, _) => MemberSharding::Partitioned { axis: 0 },
489 (ProjectionSharding::Row, _) => MemberSharding::Partitioned { axis: 1 },
490 };
491 members.push(ParameterMemberSpec::new(
492 member.target,
493 member.global_shape,
494 sharding,
495 ));
496 }
497 }
498 partitioned_group_with_preferred_units(logical_name, role, preferred_units, members)
499}
500
501pub fn segmented_projection_group<T, M>(
508 logical_name: impl Into<String>,
509 role: ParameterRole,
510 fused: &M,
511 row: &M,
512 segments: Vec<Range<usize>>,
513 preferred_units: usize,
514) -> Result<ParameterGroupSpec, ParallelPlanError>
515where
516 T: Tensor,
517 M: Parameterized<T>,
518{
519 if preferred_units == 0 || segments.is_empty() {
520 return Err(ParallelPlanError::InvalidGroup(
521 "segmented projection requires positive logical units and at least one segment".into(),
522 ));
523 }
524 let mut previous_end = 0usize;
525 for segment in &segments {
526 if segment.start != previous_end || segment.start >= segment.end {
527 return Err(ParallelPlanError::InvalidGroup(format!(
528 "segmented projection ranges must be positive, contiguous, and ordered, got {segments:?}"
529 )));
530 }
531 previous_end = segment.end;
532 }
533
534 let fused_group =
535 projection_parameter_group::<T, M>("fused", role, fused, ProjectionSharding::Column)?;
536 let row_group = projection_parameter_group::<T, M>("row", role, row, ProjectionSharding::Row)?;
537 assemble_segmented_projection_group(
538 logical_name,
539 role,
540 fused_group,
541 row_group,
542 segments,
543 preferred_units,
544 previous_end,
545 )
546}
547
548#[allow(clippy::too_many_arguments)]
549fn assemble_segmented_projection_group(
550 logical_name: impl Into<String>,
551 role: ParameterRole,
552 fused_group: ParameterGroupSpec,
553 row_group: ParameterGroupSpec,
554 segments: Vec<Range<usize>>,
555 units: usize,
556 expected_fused_width: usize,
557) -> Result<ParameterGroupSpec, ParallelPlanError> {
558 let mut members = Vec::new();
559 for member in fused_group.members {
560 let dimension = member.global_shape.first().copied().ok_or_else(|| {
561 ParallelPlanError::InvalidTensor(format!(
562 "segmented projection parameter {} is scalar",
563 member.target
564 ))
565 })?;
566 if dimension != expected_fused_width {
567 return Err(ParallelPlanError::InvalidTensor(format!(
568 "segmented projection parameter {} has output dimension {dimension}, expected {expected_fused_width}",
569 member.target
570 )));
571 }
572 members.push(ParameterMemberSpec::new(
573 member.target,
574 member.global_shape,
575 MemberSharding::PartitionedSegments {
576 axis: 0,
577 segments: segments.clone(),
578 },
579 ));
580 }
581 for member in row_group.members {
582 let sharding = if member.global_shape.len() >= 2 {
583 MemberSharding::Partitioned { axis: 1 }
584 } else {
585 MemberSharding::Replicated
586 };
587 members.push(ParameterMemberSpec::new(
588 member.target,
589 member.global_shape,
590 sharding,
591 ));
592 }
593 partitioned_group_with_preferred_units(logical_name, role, units, members)
594}
595
596pub fn aligned_partition_units(
598 name: &str,
599 semantic_units: usize,
600 elements_per_unit: usize,
601 required_alignment: usize,
602) -> Result<usize, ParallelPlanError> {
603 if semantic_units == 0 || elements_per_unit == 0 || required_alignment == 0 {
604 return Err(ParallelPlanError::InvalidGroup(format!(
605 "{name} aligned partition dimensions must be positive, got units={semantic_units}, width={elements_per_unit}, alignment={required_alignment}"
606 )));
607 }
608 let units_per_partition =
609 required_alignment / greatest_common_divisor(elements_per_unit, required_alignment);
610 if !semantic_units.is_multiple_of(units_per_partition) {
611 return Err(ParallelPlanError::InvalidGroup(format!(
612 "{name} has {semantic_units} semantic units of width {elements_per_unit}, which cannot form complete alignment-{required_alignment} partitions"
613 )));
614 }
615 Ok(semantic_units / units_per_partition)
616}
617
618pub fn expand_linear_format_parameter_groups(
622 groups: Vec<ParameterGroupSpec>,
623 declaration: impl Fn(&ParameterMemberSpec) -> Result<Option<LinearFormatSpec>, ParallelPlanError>,
624) -> Result<Vec<ParameterGroupSpec>, ParallelPlanError> {
625 groups
626 .into_iter()
627 .map(|group| {
628 let mut members = Vec::new();
629 for source in group.members() {
630 members.extend(match declaration(source)? {
631 Some(declaration) => expand_linear_format_member(source, &declaration)?,
632 None => vec![source.clone()],
633 });
634 }
635 match group.partition_units() {
636 Some(units) => partitioned_group_with_preferred_units(
637 group.logical_name(),
638 group.role(),
639 units,
640 members,
641 ),
642 None => ParameterGroupSpec::new(group.logical_name(), group.role(), members),
643 }
644 })
645 .collect()
646}
647
648fn partitioned_group_with_preferred_units(
649 logical_name: impl Into<String>,
650 role: ParameterRole,
651 preferred_units: usize,
652 members: Vec<ParameterMemberSpec>,
653) -> Result<ParameterGroupSpec, ParallelPlanError> {
654 let mut units = preferred_units;
655 for member in &members {
656 match member.sharding() {
657 MemberSharding::Partitioned { axis } => {
658 let dimension = member.global_shape().get(*axis).ok_or_else(|| {
659 ParallelPlanError::InvalidTensor(format!(
660 "partitioned parameter {} has no axis {axis}",
661 member.target()
662 ))
663 })?;
664 units = greatest_common_divisor(units, *dimension);
665 }
666 MemberSharding::PartitionedSegments { axis, segments }
667 | MemberSharding::Segmented { axis, segments } => {
668 if member.global_shape().get(*axis).is_none() {
669 return Err(ParallelPlanError::InvalidTensor(format!(
670 "segmented parameter {} has no axis {axis}",
671 member.target()
672 )));
673 }
674 for segment in segments {
675 units = greatest_common_divisor(units, segment.len());
676 }
677 }
678 MemberSharding::Replicated
679 | MemberSharding::Equal { .. }
680 | MemberSharding::Balanced { .. } => {}
681 }
682 }
683 ParameterGroupSpec::partitioned(logical_name, role, units, members)
684}
685
686fn remap_linear_segments(
687 sharding: &MemberSharding,
688 axis: usize,
689 divisor: usize,
690 name: &str,
691) -> Result<MemberSharding, ParallelPlanError> {
692 let remap = |segments: &[Range<usize>]| {
693 segments
694 .iter()
695 .map(|segment| {
696 if !segment.start.is_multiple_of(divisor) || !segment.end.is_multiple_of(divisor) {
697 return Err(ParallelPlanError::InvalidTensor(format!(
698 "packed companion {name} segment {segment:?} is not aligned to {divisor}"
699 )));
700 }
701 Ok(segment.start / divisor..segment.end / divisor)
702 })
703 .collect::<Result<Vec<_>, _>>()
704 };
705 match sharding {
706 MemberSharding::PartitionedSegments {
707 axis: selected,
708 segments,
709 } if *selected == axis => Ok(MemberSharding::PartitionedSegments {
710 axis: *selected,
711 segments: remap(segments)?,
712 }),
713 MemberSharding::Segmented {
714 axis: selected,
715 segments,
716 } if *selected == axis => Ok(MemberSharding::Segmented {
717 axis: *selected,
718 segments: remap(segments)?,
719 }),
720 other => Ok(other.clone()),
721 }
722}
723
724fn expand_linear_format_member(
725 source: &ParameterMemberSpec,
726 declaration: &LinearFormatSpec,
727) -> Result<Vec<ParameterMemberSpec>, ParallelPlanError> {
728 let name = source.target();
729 let shape = source.global_shape();
730 let format = declaration.encoding();
731 if format == LinearFormat::Dense {
732 return if declaration.scale().is_none() && declaration.affine_bias().is_none() {
733 Ok(vec![source.clone()])
734 } else {
735 Err(ParallelPlanError::InvalidGroup(format!(
736 "dense linear parameter {name} declares physical companions"
737 )))
738 };
739 }
740 if shape.len() < 2 {
741 return Err(ParallelPlanError::InvalidTensor(format!(
742 "encoded linear parameter {name} must have at least two dimensions"
743 )));
744 }
745 let row_axis = shape.len() - 2;
746 let column_axis = shape.len() - 1;
747 let invalid = |detail: String| ParallelPlanError::InvalidTensor(detail);
748 match format {
749 LinearFormat::Dense => unreachable!(),
750 LinearFormat::E4M3BlockFp8(fp8) => {
751 let Some(scale) = declaration.scale() else {
752 return Err(ParallelPlanError::InvalidGroup(format!(
753 "block-FP8 linear parameter {name} must declare exactly one scale companion"
754 )));
755 };
756 if declaration.affine_bias().is_some() {
757 return Err(ParallelPlanError::InvalidGroup(format!(
758 "block-FP8 linear parameter {name} must not declare an affine-bias companion"
759 )));
760 }
761 fp8.validate().map_err(|error| invalid(error.to_string()))?;
762 let rows = usize::try_from(fp8.block_rows)
763 .map_err(|_| invalid(format!("invalid block rows for {name}")))?;
764 let columns = usize::try_from(fp8.block_columns)
765 .map_err(|_| invalid(format!("invalid block columns for {name}")))?;
766 let mut scale_shape = shape.to_vec();
767 scale_shape[row_axis] = scale_shape[row_axis].div_ceil(rows);
768 scale_shape[column_axis] = scale_shape[column_axis].div_ceil(columns);
769 let scale_sharding = remap_linear_segments(source.sharding(), row_axis, rows, name)
770 .and_then(|value| remap_linear_segments(&value, column_axis, columns, name))?;
771 Ok(vec![
772 source.clone(),
773 ParameterMemberSpec::new(scale.id.as_str(), scale_shape, scale_sharding),
774 ])
775 }
776 LinearFormat::GgufIQuant { ggml_type, .. } => {
777 if declaration.scale().is_some() || declaration.affine_bias().is_some() {
778 return Err(ParallelPlanError::InvalidGroup(format!(
779 "GGUF linear parameter {name} must not declare companion tensors"
780 )));
781 }
782 let (block_values, block_bytes) = ggml_type
783 .block_and_bytes()
784 .map_err(|error| invalid(error.to_string()))?;
785 let block_values = usize::try_from(block_values)
786 .map_err(|_| invalid(format!("GGUF block width for {name} exceeds usize")))?;
787 let block_bytes = usize::try_from(block_bytes)
788 .map_err(|_| invalid(format!("GGUF block bytes for {name} exceeds usize")))?;
789 let input = shape[column_axis];
790 if !input.is_multiple_of(block_values) {
791 return Err(invalid(format!(
792 "GGUF matrix {name} input {input} is not aligned to block {block_values}"
793 )));
794 }
795 let mut packed = shape.to_vec();
796 packed[column_axis] = input / block_values * block_bytes;
797 Ok(vec![ParameterMemberSpec::new(
798 name,
799 packed,
800 remap_linear_segments(source.sharding(), column_axis, block_values, name)?,
801 )])
802 }
803 LinearFormat::Affine(_) | LinearFormat::MxFp4 => {
804 let quantization = format.weight_quantization().expect("packed format");
805 let Some(scale) = declaration.scale() else {
806 return Err(ParallelPlanError::InvalidGroup(format!(
807 "packed linear parameter {name} must declare a scale companion"
808 )));
809 };
810 let bias = declaration.affine_bias();
811 if quantization.has_biases() != bias.is_some() {
812 return Err(ParallelPlanError::InvalidGroup(format!(
813 "packed linear parameter {name} declares companions inconsistent with its format"
814 )));
815 }
816 let bits = usize::try_from(quantization.bits())
817 .map_err(|_| invalid(format!("packed bit width for {name} exceeds usize")))?;
818 let group = usize::try_from(quantization.group_size())
819 .map_err(|_| invalid(format!("packed group width for {name} exceeds usize")))?;
820 let input = shape[column_axis];
821 let packed_bits = input
822 .checked_mul(bits)
823 .ok_or_else(|| invalid(format!("packed matrix {name} overflows")))?;
824 if group == 0 || !input.is_multiple_of(group) || !packed_bits.is_multiple_of(32) {
825 return Err(invalid(format!(
826 "packed matrix {name} input {input} is incompatible with group {group} and {bits} bits"
827 )));
828 }
829 let mut packed = shape.to_vec();
830 packed[column_axis] = packed_bits / 32;
831 let mut companion = shape.to_vec();
832 companion[column_axis] = input / group;
833 let mut members = vec![ParameterMemberSpec::new(
834 name,
835 packed,
836 remap_linear_segments(source.sharding(), column_axis, 32 / bits, name)?,
837 )];
838 let companion_sharding =
839 remap_linear_segments(source.sharding(), column_axis, group, name)?;
840 members.push(ParameterMemberSpec::new(
841 scale.id.as_str(),
842 companion.clone(),
843 companion_sharding.clone(),
844 ));
845 if let Some(bias) = bias {
846 members.push(ParameterMemberSpec::new(
847 bias.id.as_str(),
848 companion,
849 companion_sharding,
850 ));
851 }
852 Ok(members)
853 }
854 }
855}
856
857const fn greatest_common_divisor(mut left: usize, mut right: usize) -> usize {
858 while right != 0 {
859 let remainder = left % right;
860 left = right;
861 right = remainder;
862 }
863 left
864}
865
866#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
868pub enum ShardingPolicy {
869 #[default]
871 Require,
872 ReplicateUnsupported,
874}
875
876#[derive(Debug, Clone, Eq, PartialEq)]
878pub enum TensorPlacement {
879 Replicated,
881 Local,
883 Omit,
885 Rank {
887 rank: usize,
889 },
890 Shard {
892 axis: usize,
894 index: usize,
896 parts: usize,
898 },
899 Range {
901 axis: usize,
903 start: usize,
905 end: usize,
907 },
908 Indices {
910 axis: usize,
912 indices: Vec<usize>,
914 },
915}
916
917#[derive(Debug, Clone, Eq, PartialEq)]
919pub struct LocalTensorLayout<P = TensorPlacement> {
920 logical_name: String,
921 role: ParameterRole,
922 global_shape: Vec<usize>,
923 local_shape: Vec<usize>,
924 placement: P,
925 logical_units: Option<usize>,
926 logical_range: Option<Range<usize>>,
927 fell_back_to_replication: bool,
928}
929
930impl<P> LocalTensorLayout<P> {
931 #[allow(clippy::too_many_arguments)]
933 pub fn new(
934 logical_name: impl Into<String>,
935 role: ParameterRole,
936 global_shape: Vec<usize>,
937 local_shape: Vec<usize>,
938 placement: P,
939 logical_units: Option<usize>,
940 logical_range: Option<Range<usize>>,
941 fell_back_to_replication: bool,
942 ) -> Self {
943 Self {
944 logical_name: logical_name.into(),
945 role,
946 global_shape,
947 local_shape,
948 placement,
949 logical_units,
950 logical_range,
951 fell_back_to_replication,
952 }
953 }
954
955 pub fn logical_name(&self) -> &str {
957 &self.logical_name
958 }
959
960 pub const fn role(&self) -> ParameterRole {
962 self.role
963 }
964
965 pub fn global_shape(&self) -> &[usize] {
967 &self.global_shape
968 }
969
970 pub fn local_shape(&self) -> &[usize] {
972 &self.local_shape
973 }
974
975 pub const fn placement(&self) -> &P {
977 &self.placement
978 }
979
980 pub fn logical_range(&self) -> Option<&Range<usize>> {
982 self.logical_range.as_ref()
983 }
984
985 pub const fn logical_units(&self) -> Option<usize> {
987 self.logical_units
988 }
989
990 pub const fn fell_back_to_replication(&self) -> bool {
992 self.fell_back_to_replication
993 }
994}
995
996#[derive(Debug, Clone)]
998pub struct LocalModelLayout<P = TensorPlacement> {
999 tensors: BTreeMap<String, LocalTensorLayout<P>>,
1000}
1001
1002impl<P> Default for LocalModelLayout<P> {
1003 fn default() -> Self {
1004 Self {
1005 tensors: BTreeMap::new(),
1006 }
1007 }
1008}
1009
1010impl<P> LocalModelLayout<P> {
1011 pub fn contains(&self, target: &str) -> bool {
1013 self.tensors.contains_key(target)
1014 }
1015
1016 pub fn insert(&mut self, target: String, layout: LocalTensorLayout<P>) {
1018 self.tensors.insert(target, layout);
1019 }
1020
1021 pub fn tensor(&self, target: &str) -> Option<&LocalTensorLayout<P>> {
1023 self.tensors.get(target)
1024 }
1025
1026 pub fn tensors(&self) -> impl Iterator<Item = (&str, &LocalTensorLayout<P>)> {
1028 self.tensors
1029 .iter()
1030 .map(|(target, layout)| (target.as_str(), layout))
1031 }
1032
1033 pub fn len(&self) -> usize {
1035 self.tensors.len()
1036 }
1037
1038 pub fn is_empty(&self) -> bool {
1040 self.tensors.is_empty()
1041 }
1042}
1043
1044#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
1046pub enum ParallelPlanError {
1047 #[error("{0}")]
1049 InvalidGroup(String),
1050 #[error("{0}")]
1052 InvalidTensor(String),
1053}
1054
1055#[cfg(test)]
1056mod tests {
1057 use eredu_checkpoint::{BlockFp8Format, BlockFp8ScaleEncoding};
1058
1059 use super::*;
1060
1061 #[test]
1062 fn groups_reject_duplicate_physical_targets() {
1063 let error = ParameterGroupSpec::new(
1064 "attention",
1065 ParameterRole::AttentionHeads,
1066 [
1067 ParameterMemberSpec::new("q.weight", [8, 8], MemberSharding::Replicated),
1068 ParameterMemberSpec::new("q.weight", [8, 8], MemberSharding::Replicated),
1069 ],
1070 )
1071 .unwrap_err();
1072 assert!(error.to_string().contains("repeats tensor target"));
1073 }
1074
1075 #[test]
1076 fn group_partition_contract_is_explicit() {
1077 assert!(ParameterGroupSpec::new(
1078 "query",
1079 ParameterRole::AttentionHeads,
1080 [ParameterMemberSpec::new(
1081 "q.weight",
1082 [8, 8],
1083 MemberSharding::Partitioned { axis: 0 },
1084 )],
1085 )
1086 .is_err());
1087 assert!(ParameterGroupSpec::partitioned(
1088 "query",
1089 ParameterRole::AttentionHeads,
1090 4,
1091 [ParameterMemberSpec::new(
1092 "q.weight",
1093 [8, 8],
1094 MemberSharding::Partitioned { axis: 0 },
1095 )],
1096 )
1097 .is_ok());
1098 }
1099
1100 #[test]
1101 fn preferred_partition_units_follow_every_physical_companion() {
1102 let group = partitioned_group_with_preferred_units(
1103 "experts.intermediate",
1104 ParameterRole::ExpertIntermediate,
1105 64,
1106 vec![
1107 ParameterMemberSpec::new(
1108 "experts.up_proj",
1109 [4, 64, 16],
1110 MemberSharding::Partitioned { axis: 1 },
1111 ),
1112 ParameterMemberSpec::new(
1113 "experts.down_proj",
1114 [4, 16, 8],
1115 MemberSharding::Partitioned { axis: 2 },
1116 ),
1117 ParameterMemberSpec::new(
1118 "experts.down_proj_scales",
1119 [4, 16, 2],
1120 MemberSharding::Partitioned { axis: 2 },
1121 ),
1122 ],
1123 )
1124 .unwrap();
1125
1126 assert_eq!(group.partition_units(), Some(2));
1127 }
1128
1129 #[test]
1130 fn fp8_expansion_uses_architecture_declared_companion_identity() {
1131 let groups = vec![ParameterGroupSpec::partitioned(
1132 "query",
1133 ParameterRole::AttentionHeads,
1134 8,
1135 [ParameterMemberSpec::new(
1136 "opaque_matrix",
1137 [256, 256],
1138 MemberSharding::Partitioned { axis: 0 },
1139 )],
1140 )
1141 .unwrap()];
1142 let format = LinearFormat::E4M3BlockFp8(
1143 BlockFp8Format::new(128, 128, BlockFp8ScaleEncoding::Ue8m0).unwrap(),
1144 );
1145
1146 let expanded = expand_linear_format_parameter_groups(groups, |_| {
1147 Ok(Some(
1148 LinearFormatSpec::scaled(
1149 format,
1150 eredu_nn::ParameterSpec::trainable("opaque_scale").unwrap(),
1151 )
1152 .unwrap(),
1153 ))
1154 })
1155 .unwrap();
1156 assert_eq!(expanded.len(), 1);
1157 assert_eq!(expanded[0].partition_units(), Some(2));
1158 assert_eq!(expanded[0].members().len(), 2);
1159 assert_eq!(
1160 expanded[0]
1161 .members()
1162 .iter()
1163 .map(ParameterMemberSpec::target)
1164 .collect::<Vec<_>>(),
1165 ["opaque_matrix", "opaque_scale"]
1166 );
1167 assert_eq!(expanded[0].members()[1].global_shape(), [2, 2]);
1168 assert_eq!(
1169 expanded[0].members()[1].sharding(),
1170 &MemberSharding::Partitioned { axis: 0 }
1171 );
1172 }
1173
1174 #[test]
1175 fn segmented_projection_applies_identical_ranges_to_every_fused_companion() {
1176 let fused = ParameterGroupSpec::new(
1177 "fused",
1178 ParameterRole::FeedForwardIntermediate,
1179 [
1180 ParameterMemberSpec::new(
1181 "gate_up.weight",
1182 [12, 8],
1183 MemberSharding::Equal { axis: 0 },
1184 ),
1185 ParameterMemberSpec::new(
1186 "gate_up.scales",
1187 [12, 2],
1188 MemberSharding::Equal { axis: 0 },
1189 ),
1190 ParameterMemberSpec::new(
1191 "gate_up.biases",
1192 [12, 2],
1193 MemberSharding::Equal { axis: 0 },
1194 ),
1195 ],
1196 )
1197 .unwrap();
1198 let row = ParameterGroupSpec::new(
1199 "row",
1200 ParameterRole::FeedForwardIntermediate,
1201 [
1202 ParameterMemberSpec::new("down.weight", [8, 6], MemberSharding::Equal { axis: 1 }),
1203 ParameterMemberSpec::new("down.scales", [8, 2], MemberSharding::Equal { axis: 1 }),
1204 ParameterMemberSpec::new("down.bias", [8], MemberSharding::Replicated),
1205 ],
1206 )
1207 .unwrap();
1208 let segments = vec![0..4, 4..8, 8..12];
1209 let group = assemble_segmented_projection_group(
1210 "mlp.projections",
1211 ParameterRole::FeedForwardIntermediate,
1212 fused,
1213 row,
1214 segments.clone(),
1215 2,
1216 12,
1217 )
1218 .unwrap();
1219 assert_eq!(group.partition_units(), Some(2));
1220 for member in &group.members()[..3] {
1221 assert_eq!(
1222 member.sharding(),
1223 &MemberSharding::PartitionedSegments {
1224 axis: 0,
1225 segments: segments.clone(),
1226 }
1227 );
1228 }
1229 assert_eq!(
1230 group.members()[3].sharding(),
1231 &MemberSharding::Partitioned { axis: 1 }
1232 );
1233 assert_eq!(
1234 group.members()[4].sharding(),
1235 &MemberSharding::Partitioned { axis: 1 }
1236 );
1237 assert_eq!(group.members()[5].sharding(), &MemberSharding::Replicated);
1238 }
1239
1240 #[test]
1241 fn segmented_projection_rejects_one_misaligned_companion_atomically() {
1242 let fused = ParameterGroupSpec::new(
1243 "fused",
1244 ParameterRole::AttentionHeads,
1245 [
1246 ParameterMemberSpec::new("qkv.weight", [12, 8], MemberSharding::Equal { axis: 0 }),
1247 ParameterMemberSpec::new("qkv.scales", [11, 2], MemberSharding::Equal { axis: 0 }),
1248 ],
1249 )
1250 .unwrap();
1251 let row = ParameterGroupSpec::new(
1252 "row",
1253 ParameterRole::AttentionHeads,
1254 [ParameterMemberSpec::new(
1255 "output.weight",
1256 [8, 4],
1257 MemberSharding::Equal { axis: 1 },
1258 )],
1259 )
1260 .unwrap();
1261 assert!(matches!(
1262 assemble_segmented_projection_group(
1263 "attention.projections",
1264 ParameterRole::AttentionHeads,
1265 fused,
1266 row,
1267 vec![0..4, 4..8, 8..12],
1268 2,
1269 12,
1270 ),
1271 Err(ParallelPlanError::InvalidTensor(_))
1272 ));
1273 }
1274
1275 #[test]
1276 fn parallel_model_info_preserves_opaque_topology_and_accounting() {
1277 let info = ParallelModelInfo::new(
1278 (2usize, 1usize),
1279 "generic",
1280 vec!["layer.weight".into()],
1281 10,
1282 20,
1283 4,
1284 8,
1285 );
1286 assert_eq!(info.topology(), (2, 1));
1287 assert_eq!(info.effective_model_type(), "generic");
1288 assert_eq!(info.owned_tensors(), ["layer.weight"]);
1289 assert_eq!(info.local_parameter_bytes(), 10);
1290 assert_eq!(info.global_parameter_bytes(), 20);
1291 assert_eq!(info.pinned_device_parameter_bytes(), 4);
1292 assert_eq!(info.maximum_device_parameter_bytes(), 8);
1293 }
1294}