Repository navigation
Expand file tree
/
Copy pathtime_calculation_LLM.py
More file actions
2352 lines (2083 loc) · 112 KB
/
Copy pathtime_calculation_LLM.py
File metadata and controls
2352 lines (2083 loc) · 112 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import math
import os
import json
from enum import Enum
from typing import Any, Dict, Tuple, Optional, List
import simulate_LLM
from LLM_execution import ExecutionMode, LLMExecutionDispatcher
from simulate_LLM import Graph
import LLM_util
from time_calculation import TimeCalculation
from itertools import zip_longest # for element-wise aggregation of memory access lists
def _env_flag(name: str) -> bool:
value = os.environ.get(name)
if value is None:
return False
normalized = value.strip().lower()
return normalized not in {"", "0", "false", "no"}
class ParallelismMode(Enum):
TENSOR = "tensor"
TENSOR_SEQUENCE = "tensor_sequence"
CONTEXT = "context"
TENSOR_CONTEXT_HYBRID = "tensor_context_hybrid"
SINGLE = "single"
# TODO: verify all 4 of these
SWIGLU_SILU_FORWARD_FLOPS_PER_ELEMENT = 10
SWIGLU_SILU_BACKWARD_FLOPS_PER_ELEMENT = 20
GELU_FORWARD_FLOPS_PER_ELEMENT = 10
GELU_BACKWARD_FLOPS_PER_ELEMENT = 20
LAYER_NORM_FORWARD_FLOPS_PER_ELEMENT = 7
LAYER_NORM_BACKWARD_FLOPS_PER_ELEMENT = 14
LAYER_NORM_FORWARD_MEM_ACCESSES = 2
LAYER_NORM_BACKWARD_MEM_ACCESSES = 4
SOFTMAX_FORWARD_FLOPS_PER_ELEMENT = 7 # exponentiation 4FLOPS, subtract max 1FLOPS, dropout 2FLOPS
SOFTMAX_FORWARD_MEM_ACCESSES = 4
SOFTMAX_BACKWARD_FLOPS_PER_ELEMENT = 4
SOFTMAX_BACKWARD_MEM_ACCESSES = 3
# Map each parallelism mode to operation-level collective specs used across the
# metadata pipeline. Each spec records the collective kind, the participant
# scope (tp/cp/seq/etc.), the interconnect label, and an identifying suffix.
COMM_RULE_DEFAULT_KEY = "__default__"
COMMUNICATION_RULES: Dict[
ParallelismMode, Dict[str, Dict[str, Optional[Dict[str, str]]]]
] = {
ParallelismMode.TENSOR: {
COMM_RULE_DEFAULT_KEY: {
'forward': {'kind': 'all_reduce', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'all_reduce'},
'backward': {'kind': 'all_reduce', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'all_reduce'},
},
},
ParallelismMode.TENSOR_SEQUENCE: {
COMM_RULE_DEFAULT_KEY: {'forward': None, 'backward': None},
'layernorm1': {
'forward': {'kind': 'all_gather', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'all_gather'},
'backward': {'kind': 'all_gather', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'all_gather'},
},
'layernorm2': {
'forward': {'kind': 'all_gather', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'all_gather'},
'backward': {'kind': 'all_gather', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'all_gather'},
},
'MHA': {
'forward': {'kind': 'reduce_scatter', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'reduce_scatter'},
'backward': {'kind': 'reduce_scatter', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'reduce_scatter'},
},
'MLP': {
'forward': {'kind': 'reduce_scatter', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'reduce_scatter'},
'backward': {'kind': 'reduce_scatter', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'reduce_scatter'},
},
},
ParallelismMode.CONTEXT: {
COMM_RULE_DEFAULT_KEY: {
'forward': {'kind': 'all_gather', 'participants': 'cp', 'interconnect': 'cp', 'suffix': 'all_gather'},
'backward': {'kind': 'reduce_scatter', 'participants': 'cp', 'interconnect': 'cp', 'suffix': 'reduce_scatter'},
},
'attention': {'forward': None, 'backward': {'kind': 'reduce_scatter', 'participants': 'cp', 'interconnect': 'cp', 'suffix': 'reduce_scatter'}},
'output_proj': {'forward': None, 'backward': {'kind': 'all_gather', 'participants': 'cp', 'interconnect': 'cp', 'suffix': 'all_gather'}},
'qkv_proj': {'forward': {'kind': 'all_gather', 'participants': 'cp', 'interconnect': 'cp', 'suffix': 'all_gather'}, 'backward': None},
},
ParallelismMode.TENSOR_CONTEXT_HYBRID: {
COMM_RULE_DEFAULT_KEY: {'forward': None, 'backward': None},
'layernorm1': {
'forward': {'kind': 'all_gather', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'all_gather'},
'backward': {'kind': 'all_gather', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'all_gather'},
},
'layernorm2': {
'forward': {'kind': 'all_gather', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'all_gather'},
'backward': {'kind': 'all_gather', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'all_gather'},
},
'MLP': {
'forward': {'kind': 'reduce_scatter', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'reduce_scatter'},
'backward': {'kind': 'reduce_scatter', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'reduce_scatter'},
},
'attention': {'forward': None, 'backward': {'kind': 'reduce_scatter', 'participants': 'cp', 'interconnect': 'cp', 'suffix': 'reduce_scatter'}},
'output_proj': {
'forward': {'kind': 'reduce_scatter', 'participants': 'tp', 'interconnect': 'cp', 'suffix': 'reduce_scatter'},
'backward': {'kind': 'all_gather', 'participants': 'cp', 'interconnect': 'cp', 'suffix': 'all_gather'},
},
'qkv_proj': {
'forward': {'kind': 'all_gather', 'participants': 'cp', 'interconnect': 'tp', 'suffix': 'all_gather'},
'backward': {'kind': 'reduce_scatter', 'participants': 'tp', 'interconnect': 'tp', 'suffix': 'reduce_scatter'},
},
},
ParallelismMode.SINGLE: {
COMM_RULE_DEFAULT_KEY: {'forward': None, 'backward': None},
},
}
class GemmType(Enum):
ATTENTION_SCORE = "attention_score"
ATTENTION_OUTPUT = "attention_output"
QKV = "qkv"
OUT_PROJ = "out_proj"
FFN1 = "ffn1"
FFN2 = "ffn2"
LINEAR_SOFTMAX = "linear_softmax"
class TimeCalculationLLM(TimeCalculation):
def __init__(self, hw_config, model_config, mode, output_dir: Optional[str] = None):
# Mode parameter
execution_mode = self._derive_execution_mode(hw_config)
astra_policy = self._map_execution_mode_to_policy(execution_mode)
super().__init__(
hw_config,
model_config,
mode,
astra_policy_override=astra_policy,
)
if self.num_workers > 1 and self.use_moe:
raise ValueError("MoE is only supported for single-worker runs (system_hierarchy.num_workers must be 1).")
self.output_dir = os.path.abspath(output_dir) if output_dir else os.getcwd()
os.makedirs(self.output_dir, exist_ok=True)
self._generate_graphs = _env_flag("DEEPFLOW_VISUALIZE_GRAPHS")
self.persist_astrasim_artifacts = _env_flag("DEEPFLOW_PERSIST_ASTRASIM_ARTIFACTS")
self._debug_memory = _env_flag("DEEPFLOW_DEBUG_MEMORY")
self._memory_breakdown_debug = None
self.execution_mode = execution_mode
inference_cfg = getattr(hw_config, "inference_config", None)
self.all_reduce = "every layer"
self.model_type = self.model.model_type
self.tied_embeddings = getattr(self.model, "tied_embeddings", True)
self.memory_capacity_exceeded = False
self.memory_capacity_violation_gb = 0.0
self.zero3_ephemeral_peak_bytes = 0.0
self.pipeline_graph: Optional[Graph] = None
self.pipeline_root: Optional[Any] = None
self.pipeline_interconnect: Optional[Dict[str, Tuple[float, float]]] = None
self.transformer_graph: Optional[Graph] = None
self.transformer_forward_root: Optional[Any] = None
self.transformer_backward_root: Optional[Any] = None
self.transformer_analytical_time_forward: Optional[float] = None
self.transformer_analytical_time_backward: Optional[float] = None
self.transformer_astrasim_time_forward: Optional[float] = None
self.transformer_astrasim_time_backward: Optional[float] = None
self.transformer_astrasim_per_rank_forward: Optional[List[float]] = None
self.transformer_astrasim_per_rank_backward: Optional[List[float]] = None
self.pipeline_astrasim_time: Optional[float] = None
self.pipeline_astrasim_per_rank: Optional[List[float]] = None
def _sequence_parallel_degree(self) -> int:
"""Return tensor-parallel degree used for sequence-parallel collectives.
return 1 if no sequence parallelism is used.
return tp if tp-sp is True and no context parallelism is used
return cp if context parallelism is used
"""
if self.tp_sp and self.cp == 1: #tensor parallelism only
return self.tp
elif self.cp > 1: #context parallelism or cp-tp hybrid parallelism
return self.cp
else:
return 1
def get_parallelism_mode(self):
if self.tp_sp and self.tp > 1 and self.cp == 1:
return ParallelismMode.TENSOR_SEQUENCE
elif self.tp > 1 and self.cp == 1:
return ParallelismMode.TENSOR
elif self.cp > 1 and self.tp == 1:
return ParallelismMode.CONTEXT
elif self.cp > 1 and self.tp > 1:
return ParallelismMode.TENSOR_CONTEXT_HYBRID
else:
return ParallelismMode.SINGLE
def _param_stats_per_rank(
self,
hidden_dim: int,
intermediate_size: int,
vocab_size: int,
) -> Tuple[float, float, float, float, float]:
"""Return detailed per-rank parameter counts used for ZeRO modeling."""
tp = max(1, self.tp)
lp = max(1, self.lp)
ffn_proj_factor = 3 if str(self.model_type).lower() == "llama" else 2
transformer_param_layer = 4 * hidden_dim * hidden_dim + intermediate_size * ffn_proj_factor * hidden_dim
params_per_layer_per_rank = transformer_param_layer / tp
total_transformer_params = params_per_layer_per_rank * self.num_layers
if lp == 1:
transformer_params_local = total_transformer_params
else:
layers_per_stage = math.ceil(self.num_layers / lp)
transformer_params_local = params_per_layer_per_rank * layers_per_stage
transformer_params_local = min(transformer_params_local, total_transformer_params)
embedding_params = vocab_size * hidden_dim / tp
output_params = 0.0 if self.tied_embeddings else embedding_params
if lp == 1:
total_params_per_rank = transformer_params_local + embedding_params + output_params
else:
# Pipeline splits embeddings/output across stages. Approximate their contribution by spreading across stages.
total_params_per_rank = transformer_params_local + (embedding_params / lp) + (output_params / lp)
max_layer_params = max(params_per_layer_per_rank, embedding_params, output_params)
return (
total_params_per_rank,
max_layer_params,
params_per_layer_per_rank,
embedding_params,
output_params,
)
def get_kv_size_bytes(self) -> int:
"""Return the total size in bytes of the KV cache."""
total_elements = 2 * self.seq_len * self.microB * self.hidden_dim / self.num_heads * self.kv_heads
return total_elements * self.precision.kv_cache
@staticmethod
def _derive_execution_mode(hw_config) -> ExecutionMode:
backend = getattr(hw_config, "execution_backend", None)
if not backend or getattr(backend, "model", "analytical").lower() != "astra":
return ExecutionMode.ANALYTICAL
mode_str = "hybrid"
astra_cfg = getattr(backend, "astra", None)
if astra_cfg and getattr(astra_cfg, "mode", None):
mode_str = str(astra_cfg.mode).lower()
for candidate in ExecutionMode:
if candidate.value == mode_str:
return candidate
print(f"[WARN] Unknown execution mode '{mode_str}', defaulting to 'hybrid'.")
return ExecutionMode.HYBRID
@staticmethod
def _map_execution_mode_to_policy(mode: ExecutionMode) -> str:
if mode == ExecutionMode.ANALYTICAL:
return 'analytical'
if mode in (
ExecutionMode.FULL_ASTRASIM_HIERARCHICAL,
ExecutionMode.FULL_ASTRASIM_FLATTENED,
):
return 'full'
# Hybrid still uses the analytical pipeline, so keep the hybrid policy.
return 'hybrid'
@staticmethod
def _expand_gemm_descriptor(gemm: Tuple[int, ...]) -> Tuple[int, int, int, int]:
if len(gemm) == 3:
return 1, gemm[0], gemm[1], gemm[2]
if len(gemm) == 4:
return gemm[0], gemm[1], gemm[2], gemm[3]
raise ValueError(f"Unsupported GEMM descriptor length: {len(gemm)}")
@classmethod
def _effective_dims(cls, gemm: Tuple[int, ...]) -> Tuple[int, int, int, int]:
batch, m, k, n = cls._expand_gemm_descriptor(gemm)
return batch, batch * m, k, n
def _ffn1_output_dim(self, intermediate_size: int) -> int:
return 2 * intermediate_size if self.model_type == "llama" else intermediate_size
# def sequence
def get_tensor_reduction_time(self, total_bytes: int, kind: str, name: str, participants: Optional[int] = None) -> float:
"""Return collective time for tensor-parallel reductions.
receives total_bytes, which is the total size of the data to be reduced across all participants.
"""
if not total_bytes:
return 0.0
if not participants:
participants = int(self.tp)
reduction_time = self.network_model.collective(
kind=kind,
size_bytes=total_bytes,
participants=participants,
ib=self.IBTP,
ll=self.LLTP,
local_bytes=0,
debug_label=name or "comm",
)
return reduction_time
def flash_attention_kernel_forward(self, batch_size, hidden_dim, seq_len, num_heads, kv_heads, num_SMs) -> Tuple[Dict[str, float], Dict[str, float], Dict[str, float]]:
"""Return time for flash attention kernel."""
shard_seq = math.ceil(seq_len / max(1, self.cp))
d = hidden_dim // num_heads # gemm shape for one head is (seq_len, d) x (d, seq_len)
Bc = self.attention_tile_size # kv tile size
Br = min(Bc, d) # q tile size
Tr = math.ceil(shard_seq / Br) #number of q tiles
Tc = math.ceil(shard_seq / Bc) #number of kv tiles
attention_forward_reduction_time = 0
attention_size_f = 0
# assuming key and value are preloaded into shared memory before attention computation
load_kv_bytes = Bc * d * 2 * num_SMs * self.precision.activations #load key and value for one tile from HBM to SRAM
initial_load_time = self.roofline(0, load_kv_bytes, "flash_attention_initial_load", mem_level=self.num_levels - 1) #assume key and value of one attention head is loaded from HBM to SRAM
initial_load_time_per_head = initial_load_time * math.ceil(Tc/ num_SMs)
# attention score gemm
load_q_bytes = Br * d * self.precision.activations #load query for one tile assuming k is already in shared memory
attn_score_time_per_tile = self.getGEMMTime(Br, d, Bc, "attention_score_f",read_bytes_l2=load_q_bytes, flashattn_enable=True)[0]
attn_score_time = attn_score_time_per_tile * Tc * Tr #attention score gemm time for one head
# Softmax time
elements = Br * Bc
flops = SOFTMAX_FORWARD_FLOPS_PER_ELEMENT * elements
attn_scale_softmax_time = self.roofline(flops, 1, "attention_scale_softmax", mem_level=self.num_levels - 1) * Tc * Tr #use roofline model for softmax time with no memory access, memory access set to 1 because roofline does not accept 0 memory access
# attention output gemm
output_bytes = Br * d * self.precision.activations #load value for one tile S is already in shared memory
attn_output_time_per_tile = self.getGEMMTime(Br, Bc, d, "attention_output", read_bytes_l2=output_bytes, write_bytes_l2=output_bytes, flashattn_enable=True)[0]
attn_output_time = attn_output_time_per_tile * Tc * Tr #attention output gemm time for one head
load_time = initial_load_time_per_head * batch_size * kv_heads / max(1, self.tp) #load time for key and value are needed once per kv head
attn_score_time *= batch_size * num_heads / max(1, self.tp) + self.O
attn_scale_softmax_time *= batch_size * num_heads / max(1, self.tp)
attn_output_time *= batch_size * num_heads / max(1, self.tp) + self.O
attention_forward_gemm_time = load_time + attn_score_time + attn_scale_softmax_time + attn_output_time
attention_forward_time = attention_forward_gemm_time + attention_forward_reduction_time
# HBM traffic consists of only reading Q, K, V once and writing output once
attention_mem = 2 * seq_len * (hidden_dim + d * kv_heads) * self.precision.activations
return attention_forward_time, attention_forward_gemm_time, attention_forward_reduction_time, attention_size_f, attention_mem
def flash_attention_kernel_backward(self) -> Tuple[Dict[str, float], Dict[str, float], Dict[str, float]]:
#TODO: should be repleced with a modified version of getgemmtime that considers flash attention tiling
"""Return time for flash attention kernel."""
#FIXME not finished
#TODO gqa support
batch_size = self.microB
seq_len = self.seq_len
hidden_dim = self.hidden_dim
num_heads = self.num_heads
shard_seq = math.ceil(seq_len / max(1, self.cp))
d = hidden_dim // num_heads #gemm shape for one head is (seq_len, d) x (d, seq_len)
Bc = self.attention_tile_size
Br = min(Bc, d)
Tr = math.ceil(shard_seq / Br) #number of row tiles
Tc = math.ceil(shard_seq / Bc) #number of column tiles
print(f"Flash Attention: Br={Br}, Tr={Tr}, d={d}, Bc={Bc}, Tc={Tc}")
#TODO load 3 tiled gemm inputs and 1 output from HBM to SRAM
#TODO disable HBM access
attention_forward_reduction_time = 0
attention_size_f = 0
attn_score_time = self.getGEMMTime(Br, d, Bc, "attention_score_f")[0] * Tc * Tr #attention score gemm time for one head
number_flops = Br * Bc * d * Tc * Tr * batch_size * num_heads
print(f"Flash Attention: attention score gemm flops={number_flops/1e9} GFLOPS")
# Softmax time
elements = Br * Bc
flops = SOFTMAX_FORWARD_FLOPS_PER_ELEMENT * elements # exponentiation + subtract max + dropout
attn_scale_softmax_time = self.roofline(flops, 1, "attention_scale_softmax", mem_level=self.num_levels - 1) * Tc * Tr #use roofline model for softmax time with no memory access, memory access set to 1 because roofline does not accept 0 memory access
attn_output_time = self.getGEMMTime(Br, Bc, d, "attention_output")[0] * Tc * Tr #attention output gemm time for one head
attn_score_time *= batch_size * num_heads / max(1, self.tp)
attn_scale_softmax_time *= batch_size * num_heads / max(1, self.tp)
attn_output_time *= batch_size * num_heads / max(1, self.tp)
attention_forward_gemm_time = attn_score_time + attn_scale_softmax_time + attn_output_time
attention_forward_time = attention_forward_gemm_time + attention_forward_reduction_time
attention_size_f = 0
return attention_forward_time, attention_forward_gemm_time, attention_forward_reduction_time, attention_size_f
@staticmethod
def _normalize_gemm_type(gemm_type: Optional[GemmType]) -> Optional[GemmType]:
if gemm_type is None or isinstance(gemm_type, GemmType):
return gemm_type
raise TypeError(f"Unsupported gemm type specifier: {gemm_type!r}")
# assuming no context parallelism for now
def parallelism_gemm_forward(self, gemm: Tuple[int, ...], name: str, gemm_type: Optional[GemmType] = None) -> Any:
parallelism_mode = self.get_parallelism_mode()
if parallelism_mode == ParallelismMode.TENSOR or parallelism_mode == ParallelismMode.TENSOR_SEQUENCE:
return self._tensor_parallelism_gemm_forward(gemm, name, gemm_type) # also return flops and mem accesses
elif parallelism_mode == ParallelismMode.CONTEXT:
return self._context_parallelism_gemm_forward(gemm, name, gemm_type)
elif parallelism_mode == ParallelismMode.TENSOR_CONTEXT_HYBRID:
return self._tensor_context_hybrid_gemm_forward(gemm, name, gemm_type)
elif parallelism_mode == ParallelismMode.SINGLE:
return self.single_gpu_gemm_forward(gemm, name, gemm_type) # also return flops and mem accesses
else:
raise ValueError(f"Unsupported parallelism mode: {parallelism_mode}")
def parallelism_gemm_backward(self, gemm: Tuple[int, ...], name: str, gemm_type: Optional[GemmType] = None) -> Any:
parallelism_mode = self.get_parallelism_mode()
if parallelism_mode == ParallelismMode.TENSOR or parallelism_mode == ParallelismMode.TENSOR_SEQUENCE:
return self._tensor_parallelism_gemm_backward(gemm, name, gemm_type)
elif parallelism_mode == ParallelismMode.CONTEXT:
return self._context_parallelism_gemm_backward(gemm, name, gemm_type)
elif parallelism_mode == ParallelismMode.TENSOR_CONTEXT_HYBRID:
return self._tensor_context_hybrid_gemm_backward(gemm, name, gemm_type)
elif parallelism_mode == ParallelismMode.SINGLE:
return self.single_gpu_gemm_backward(gemm, name, gemm_type)
else:
raise ValueError(f"Unsupported parallelism mode: {parallelism_mode}")
def single_gpu_gemm_forward(self, gemm: Tuple[int, ...], name: str, gemm_type: Optional[GemmType] = None) -> Tuple[float, float]:
batch, m, k, n = self._expand_gemm_descriptor(gemm)
total_flops = 2 * batch * m * k * n
mem_accesses = []
gemm_type = self._normalize_gemm_type(gemm_type)
if gemm_type in (GemmType.ATTENTION_SCORE, GemmType.ATTENTION_OUTPUT): # attention gemm
gemm_time = self.getGEMMTime(m, k, n, name, disable_overhead=True)[0] * batch + self.O
elif (gemm_type == GemmType.FFN1 or gemm_type == GemmType.FFN2) and self.use_moe:
gemm_time,_,_, mem_accesses = self.getGEMMTime(m, k, n, name)
gemm_time *= self.moe_num_experts
else :
gemm_time,_,_, mem_accesses = self.getGEMMTime(m, k, n, name)
return gemm_time, 0, 0, total_flops, mem_accesses
def single_gpu_gemm_backward(self, gemm: Tuple[int, ...], name: str, gemm_type: Optional[GemmType] = None) -> Tuple[float, float]:
batch, m, k, n = self._expand_gemm_descriptor(gemm)
gemm_type = self._normalize_gemm_type(gemm_type)
if gemm_type in (GemmType.ATTENTION_SCORE, GemmType.ATTENTION_OUTPUT): # attention gemm
grad_time_act = self.getGEMMTime(m, k, n, name, disable_overhead=True)[0] * batch + self.O
grad_time_wt = self.getGEMMTime(k, m, n, name, disable_overhead=True)[0] * batch + self.O
elif (gemm_type == GemmType.FFN1 or gemm_type == GemmType.FFN2) and self.use_moe:
grad_time_act = self.getGEMMTime(m, n, k, name)[0] * self.moe_num_experts
grad_time_wt = self.getGEMMTime(k, m, n, name)[0] * self.moe_num_experts
else :
grad_time_act = self.getGEMMTime(m, n, k, name)[0]
grad_time_wt = self.getGEMMTime(k, m, n, name)[0]
gemm_time = grad_time_act + grad_time_wt
return gemm_time, 0, 0
def _tensor_context_hybrid_gemm_forward(self, gemm: Tuple[int, ...], name: str, gemm_type: Optional[GemmType] = None) -> Tuple[float, float]:
"""
Megatron-LM style TPxCP hybrid forward GEMM behavior.
Sharding:
• CP shards the token (sequence) dimension M: shard_m = ceil(M / cp).
• TP shards either K (row-wise) or N (column-wise), depending on gemm_type:
- Column-wise (split N): QKV, FFN1, LINEAR_SOFTMAX
- Row-wise (split K): OUT_PROJ, FFN2
Per-op compute & communication:
• ATTENTION_SCORE / ATTENTION_OUTPUT:
-attention gemm shape is (shard_m, k) x (k, n) for each head, should scale with batch * num_heads / tp, number of heads is already multiplied in batch dimension
- No extra collective here (K/V movement is modeled in QKV).
• QKV (column-wise over N):
- CP all_gather(K,V) so each CP rank holds full-context K/V for attention.
• OUT_PROJ, FFN2 (row-wise over K):
- same as tensor parallelism forward gemm with row-wise sharding.:
• FFN1 (column-wise over N):
- No TP/CP collective in forward same as in tensor parallelism.
"""
batch, m, k, n = self._expand_gemm_descriptor(gemm)
participants = 0
total_bytes = 0
reduction_time = 0
shard_m = math.ceil(m / max(1, self.cp))
shard_k = math.ceil(k / max(1, self.tp))
shard_n = math.ceil(n / max(1, self.tp))
gemm_type = self._normalize_gemm_type(gemm_type)
if gemm_type is None:
raise ValueError("gemm_type is required for tensor-context hybrid forward GEMM")
if gemm_type == GemmType.ATTENTION_SCORE: # attention gemm
gemm_time = self.getGEMMTime(shard_m, k, n, name, disable_overhead=True)[0] * batch / max(1, self.tp) + self.O
elif gemm_type == GemmType.ATTENTION_OUTPUT: # attention gemm
gemm_time = self.getGEMMTime(shard_m, k, n, name, disable_overhead=True)[0] * batch / max(1, self.tp) + self.O
elif gemm_type == GemmType.QKV: # column wise
gemm_time = self.getGEMMTime(shard_m, k, shard_n, name)[0]
total_bytes = self.get_kv_size_bytes() / self.tp # each tp group holds a tp shard of kv for each cp group
kind = "all_gather"
participants = self.cp # all gather K V for each cp group
elif gemm_type == GemmType.OUT_PROJ:
gemm_time = self.getGEMMTime(shard_m, shard_k, n, name)[0]
total_bytes = math.ceil(self.precision.activations * shard_m * n)
kind = "reduce_scatter"
participants = self.tp # reduce scatter output activation for each tp group
elif gemm_type == GemmType.FFN2: # row wise
gemm_time = self.getGEMMTime(shard_m, shard_k, n, name)[0]
total_bytes = math.ceil(self.precision.activations * shard_m * n)
kind = "reduce_scatter"
participants = self.tp # reduce scatter output activation for each tp group
elif gemm_type == GemmType.FFN1: # column wise
gemm_time = self.getGEMMTime(shard_m, k, shard_n, name)[0]
else:
raise ValueError(f"Unsupported gemm type: {gemm_type}")
if total_bytes > 0:
reduction_time = self.network_model.collective(
kind=kind,
size_bytes=total_bytes,
participants=participants,
ib=self.IBTP,
ll=self.LLTP,
local_bytes=0,
debug_label=name or "comm",
)
return gemm_time, reduction_time, total_bytes
def _tensor_context_hybrid_gemm_backward(self, gemm: Tuple[int, ...], name: str, gemm_type: Optional[GemmType] = None) -> Tuple[float, float]:
"""
Megatron-LM style TPxCP hybrid backward GEMM behavior.
Sharding:
• CP shards tokens (M): shard_m = ceil(M / cp).
• TP shards either N (column-wise) or K (row-wise) per gemm_type:
- Column-wise (split N): QKV, FFN1, LINEAR_SOFTMAX (vocab proj)
- Row-wise (split K): OUT_PROJ, FFN2
- Attention inner GEMMs: per-head work scaled by 1/tp.
Backward rules (compute + comm):
• Column-wise (QKV / FFN1 / LINEAR_SOFTMAX):
- dX = dY_i @ W_i^T → shape [shard_m, K] → TP reduction on dX (sum):
use reduce-scatter on TP (keeps token sharding),
- dW_i = X^T @ dY_i → local, no TP comm
- (QKV only) in CP: dK, dV must be reduce-scattered on CP back to token owners.
Bytes ≈ get_kv_size_bytes() / tp (TP shard per CP group).
• ATTENTION_SCORE / ATTENTION_OUTPUT:
- Backward GEMMs are local on token shard with 1/tp scaling across heads.
- CP/TP collectives handled in QKV branch (for K/V only), thus no comm here.
"""
batch, m, k, n = self._expand_gemm_descriptor(gemm)
# size_bytes = 0
participants = 0
total_bytes = 0
reduction_time = 0
shard_m = math.ceil(m / max(1, self.cp))
shard_k = math.ceil(k / max(1, self.tp))
shard_n = math.ceil(n / max(1, self.tp))
gemm_type = self._normalize_gemm_type(gemm_type)
if gemm_type is None:
raise ValueError("gemm_type is required for tensor-context hybrid backward GEMM")
if gemm_type == GemmType.ATTENTION_SCORE: # attention gemm
grad_time_act = self.getGEMMTime(shard_m, n, k, name, disable_overhead=True)[0] * batch / max(1, self.tp) + self.O
grad_time_wt = self.getGEMMTime(k, shard_m, n, name, disable_overhead=True)[0] * batch / max(1, self.tp) + self.O
total_bytes = self.precision.grad_communication * k * n * batch * 2 / self.tp # weight gradient of K V need to be reduce scattered *2 account for both attn key and value
kind = "reduce_scatter"
participants = self.cp
elif gemm_type == GemmType.ATTENTION_OUTPUT: # attention gemm
grad_time_act = self.getGEMMTime(shard_m, n, k, name)[0] * batch / max(1, self.tp)
grad_time_wt = self.getGEMMTime(k, shard_m, n, name)[0] * batch / max(1, self.tp)
elif gemm_type == GemmType.QKV: # column wise
grad_time_act = self.getGEMMTime(shard_m, shard_n, k, name)[0]
grad_time_wt = self.getGEMMTime(k, shard_m, shard_n, name)[0]
total_bytes = math.ceil(self.precision.grad_communication * shard_m * k)
kind = "reduce_scatter"
participants = self.tp
elif gemm_type == GemmType.OUT_PROJ:
grad_time_act = self.getGEMMTime(shard_m, n, shard_k, name)[0]
grad_time_wt = self.getGEMMTime(shard_k, shard_m, n, name)[0]
total_bytes = self.get_kv_size_bytes() / self.tp
kind = "all_gather"
participants = self.cp
elif gemm_type == GemmType.FFN2: # row wise
grad_time_act = self.getGEMMTime(shard_m, n, shard_k, name)[0]
grad_time_wt = self.getGEMMTime(shard_k, shard_m, n, name)[0]
elif gemm_type == GemmType.FFN1: # column wise
grad_time_act = self.getGEMMTime(shard_m, shard_n, k, name)[0]
grad_time_wt = self.getGEMMTime(k, shard_m, shard_n, name)[0]
total_bytes = math.ceil(self.precision.grad_communication * shard_m * k)
kind = "reduce_scatter"
participants = self.tp
elif gemm_type == GemmType.LINEAR_SOFTMAX:
grad_time_act = self.getGEMMTime(shard_m, n, shard_k, name)[0]
grad_time_wt = self.getGEMMTime(shard_k, shard_m, n, name)[0]
total_bytes = math.ceil(self.precision.grad_communication * shard_m * shard_k) * self.cp * self.tp # in tp-cp hybrid parallelism, the linear softmax weight is sharded by both tp and cp
kind = "all_gather"
participants = self.cp * self.tp
else:
raise ValueError(f"Unsupported gemm type: {gemm_type}")
gemm_time = grad_time_act + grad_time_wt
if total_bytes > 0:
reduction_time = self.network_model.collective(
kind=kind,
size_bytes=total_bytes,
participants=participants,
ib=self.IBTP,
ll=self.LLTP,
local_bytes=0,
debug_label=name or "comm",
)
return gemm_time, reduction_time, total_bytes
def _tensor_parallelism_gemm_forward(self, gemm: Tuple[int, ...], name: str, gemm_type: Optional[GemmType] = None) -> Tuple[float, float]:
"""
Tensor-parallel forward GEMM behavior.
• Multi-Head Attention (MHA):
- With tensor parallelism, the attention computation is sharded along the head dimension,
so each TP rank handles a subset of heads. No communication is needed for
ATTENTION_SCORE / ATTENTION_OUTPUT GEMMs in the forward pass.
attention gemm time is scaled by batch / tp
• After attention, before feeding MLP:
- If TP-only, we all-reduce to gather the full attention output.
- If TP+SP (sequence parallel enabled), each TP rank keeps only a shard of the sequence
after layernorm, so we use reduce-scatter instead of all-reduce.
• Sharding rules by gemm_type:
- QKV, FFN1: **column-wise sharding** (split along output dimension N).
Each TP rank produces its local output columns independently - no communication needed.
- OUT_PROJ, FFN2: **row-wise sharding** (split along input dimension K).
Each TP rank computes partial sums that must be combined across ranks via
all-reduce or reduce-scatter.
- LINEAR_SOFTMAX (final logits projection): **column-wise sharding**.
The output projection weight [hidden_dim, vocab_size] is split by vocab dimension.
Each TP rank computes logits for its vocab slice, and results are all-gathered
"""
tp_mode = self.get_parallelism_mode()
if gemm_type == GemmType.LINEAR_SOFTMAX:
comm_kind_fwd = "all_gather"
else:
comm_kind_fwd = "all_reduce" if tp_mode == ParallelismMode.TENSOR else "reduce_scatter"
batch, m, k, n = self._expand_gemm_descriptor(gemm)
size_bytes = 0
total_bytes = 0
reduction_time = 0
total_flops = 2 * batch * m * k * n
mem_accesses = []
gemm_type = self._normalize_gemm_type(gemm_type)
if gemm_type is None:
raise ValueError("gemm_type is required for tensor-parallel forward GEMM")
if gemm_type in (GemmType.ATTENTION_SCORE, GemmType.ATTENTION_OUTPUT): # attention gemm
gemm_time,_,_, mem_accesses = self.getGEMMTime(m, k, n, name, disable_overhead=True)
gemm_time = gemm_time * batch / max(1, self.tp) + self.O
elif gemm_type in (GemmType.FFN1, GemmType.FFN2) and self.use_moe:
# MoE FFN layers replicate experts across all TP ranks, so no TP sharding here.
gemm_time,_,_, mem_accesses = self.getGEMMTime(m, k, n, name)
gemm_time *= self.moe_num_experts
elif gemm_type in (GemmType.QKV, GemmType.FFN1): # column wise
shard_n = math.ceil(n / max(1, self.tp))
gemm_time,_,_, mem_accesses = self.getGEMMTime(m, k, shard_n, name)
elif gemm_type in (GemmType.OUT_PROJ, GemmType.FFN2): # row wise
shard_k = math.ceil(k / max(1, self.tp))
gemm_time,_,_, mem_accesses = self.getGEMMTime(m, shard_k, n, name)
size_bytes = math.ceil(self.precision.activations * m * n)
participants = self.tp
elif gemm_type == GemmType.LINEAR_SOFTMAX: #assuming linear softmax is always column wise sharded
shard_n = math.ceil(n / max(1, self.tp * self.cp ))
gemm_time,_,_, mem_accesses = self.getGEMMTime(m, k, shard_n, name)
size_bytes = math.ceil(self.precision.activations * m * n)
participants = self.tp * self.cp
else:
raise ValueError(f"Unsupported gemm type: {gemm_type}")
if size_bytes > 0:
total_bytes = size_bytes #we already has the totol bytes for all reduce not bytes per rank
reduction_time = self.get_tensor_reduction_time(total_bytes, kind=comm_kind_fwd, participants=participants, name=name)
return gemm_time, reduction_time, total_bytes, total_flops, mem_accesses
def _tensor_parallelism_gemm_backward(self, gemm: Tuple[int, ...], name: str, gemm_type: Optional[GemmType] = None, comm_after: bool = False) -> Tuple[float, float]:
"""
Tensor-parallel backward GEMM behavior.
We model the time for two backward GEMMs per op:
• grad wrt activation (dX)
• grad wrt weight (dW)
• ATTENTION_SCORE / ATTENTION_OUTPUT
- Per-rank work scales with batch/tp (each rank handles a subset of heads).
• Column-wise sharded ops :
- QKV, FFN1, and LINEAR_SOFTMAX:
- Local backward GEMMs:
dX: [m, k] via (dY_i @ W_i^T)
dW: [k, n/tp] via (X^T @ dY_i)
- dX is a partial across ranks** → requires tensor reduction
(all-reduce if TP-only; reduce-scatter if TP+SP).
• Row-wise sharded ops :
- OUT_PROJ, FFN2:
- Local backward GEMMs:
dX_i: [m, k/tp] via (dY @ W_i^T) - disjoint along K, no cross-rank sum
dW_i: [k/tp, n] via (X_i^T @ dY)
- no tensor reduction on dX for row-wise in backward.
"""
batch, m, k, n = self._expand_gemm_descriptor(gemm)
seq_degree = self._sequence_parallel_degree()
act_bytes = 0
total_bytes = 0
if gemm_type == GemmType.LINEAR_SOFTMAX:
comm_kind_bwd = "all_reduce"
else:
comm_kind_bwd = "all_reduce" if seq_degree == 1 else "reduce_scatter"
gemm_type = self._normalize_gemm_type(gemm_type)
if gemm_type is None:
raise ValueError("gemm_type is required for tensor-parallel backward GEMM")
if gemm_type in (GemmType.ATTENTION_SCORE, GemmType.ATTENTION_OUTPUT):
grad_time_act = self.getGEMMTime(m, n, k, name, disable_overhead=True)[0] * batch / max(1, self.tp) + self.O
grad_time_wt = self.getGEMMTime(k, m, n, name, disable_overhead=True)[0] * batch / max(1, self.tp) + self.O
elif gemm_type in (GemmType.QKV, GemmType.FFN1): # column wise
shard_n = math.ceil(n / max(1, self.tp))
grad_time_act = self.getGEMMTime(m, shard_n, k, name)[0]
grad_time_wt = self.getGEMMTime(k, m, shard_n, name)[0]
act_bytes = math.ceil(self.precision.grad_communication * m * k)
participants = self.tp
elif gemm_type in (GemmType.OUT_PROJ, GemmType.FFN2): # row wise
shard_k = math.ceil(k / max(1, self.tp))
grad_time_act = self.getGEMMTime(m, n, shard_k, name)[0]
grad_time_wt = self.getGEMMTime(shard_k, m, n, name)[0]
elif gemm_type == GemmType.LINEAR_SOFTMAX:
shard_n = math.ceil(n / max(1, self.tp * self.cp))
grad_time_act = self.getGEMMTime(m, shard_n, k, name)[0]
grad_time_wt = self.getGEMMTime(k, m, shard_n, name)[0]
act_bytes = math.ceil(self.precision.grad_communication * m * k)
participants = self.tp * self.cp
else:
raise ValueError(f"Unsupported gemm type: {gemm_type}")
gemm_time = grad_time_act + grad_time_wt
reduction_time = 0
if act_bytes > 0:
total_bytes = act_bytes #total bytes for all reduce
reduction_time = self.get_tensor_reduction_time(total_bytes, kind=comm_kind_bwd, participants=participants, name=name)
return gemm_time, reduction_time, total_bytes
def _context_parallelism_gemm_forward(self, gemm: Tuple[int, ...], name: str, gemm_type: Optional[GemmType] = None) -> Tuple[float, float]:
"""
Megatron-LM style context-parallel (CP) forward GEMM behavior.
• CP shards the token (sequence) dimension M across cp ranks:
shard_m = ceil(M / cp). Each rank processes a disjoint subset of tokens.
• Each GEMM is performed locally on the rank’s token slice.
• Communication is required only when gathering K and V for attention,
since every rank must hold the full set of keys and values to compute
attention scores.
"""
batch, m, k, n = self._expand_gemm_descriptor(gemm)
total_bytes = 0
reduction_time = 0
shard_m = math.ceil(m / max(1, self.cp))
gemm_type = self._normalize_gemm_type(gemm_type)
if gemm_type is None:
raise ValueError("gemm_type is required for context-parallel forward GEMM")
if gemm_type in (GemmType.ATTENTION_SCORE, GemmType.ATTENTION_OUTPUT): # attention gemm
gemm_time = self.getGEMMTime(shard_m, k, n, name, disable_overhead=True)[0] * batch + self.O
elif gemm_type == GemmType.QKV: # qkv gemm
gemm_time = self.getGEMMTime(shard_m, k, n, name)[0]
total_bytes = self.get_kv_size_bytes()
elif gemm_type in (GemmType.OUT_PROJ, GemmType.FFN1, GemmType.FFN2):
gemm_time = self.getGEMMTime(shard_m, k, n, name)[0]
else:
raise ValueError(f"Unsupported gemm type: {gemm_type}")
if gemm_type == GemmType.QKV:
kind = "all_gather"
reduction_time = self.network_model.collective(
kind=kind,
size_bytes=total_bytes,
participants=self.cp,
ib=self.IBTP,
ll=self.LLTP,
local_bytes=0,
debug_label=name or "comm",
)
return gemm_time, reduction_time, total_bytes
def _context_parallelism_gemm_backward(self, gemm: Tuple[int, ...], name: str, gemm_type: Optional[GemmType] = None, comm_after: bool = False) -> Tuple[float, float]:
"""
Megatron-LM style context-parallel (CP) backward GEMM behavior.
Communication rules in CP (sequence-parallel) backward:
• QKV projection:
- Forward did an all-gather(K, V) across cp ranks.
- Backward must return gradients to token owners:
dK, dV → reduce-scatter over cp ranks.
• Output projection:
- K V need to be gathered again to compute activation gradients.
"""
batch, m, k, n = self._expand_gemm_descriptor(gemm)
total_bytes = 0
reduction_time = 0
shard_m = math.ceil(m / max(1, self.cp))
gemm_type = self._normalize_gemm_type(gemm_type)
if gemm_type is None:
raise ValueError("gemm_type is required for context-parallel backward GEMM")
if gemm_type == GemmType.ATTENTION_SCORE:
grad_time_act = self.getGEMMTime(shard_m, n, k, name, disable_overhead=True)[0] * batch + self.O
grad_time_wt = self.getGEMMTime(k, shard_m, n, name, disable_overhead=True)[0] * batch + self.O
total_bytes = self.precision.grad_communication * k * n * batch * 2 # account for both Q and K
kind = "reduce_scatter"
elif gemm_type == GemmType.ATTENTION_OUTPUT: # attention gemm
grad_time_act = self.getGEMMTime(shard_m, n, k, name, disable_overhead=True)[0] * batch + self.O
grad_time_wt = self.getGEMMTime(k, shard_m, n, name, disable_overhead=True)[0] * batch + self.O
elif gemm_type in (GemmType.QKV, GemmType.FFN1, GemmType.FFN2):
grad_time_act = self.getGEMMTime(shard_m, n, k, name)[0]
grad_time_wt = self.getGEMMTime(k, shard_m, n, name)[0]
elif gemm_type == GemmType.OUT_PROJ:
grad_time_act = self.getGEMMTime(shard_m, n, k, name)[0]
grad_time_wt = self.getGEMMTime(k, shard_m, n, name)[0]
total_bytes = self.get_kv_size_bytes()
kind = "all_gather"
else:
raise ValueError(f"Unsupported gemm type: {gemm_type}")
gemm_time = grad_time_act + grad_time_wt
if total_bytes > 0:
reduction_time = self.network_model.collective(
kind=kind,
size_bytes=total_bytes,
participants=self.cp,
ib=self.IBTP,
ll=self.LLTP,
local_bytes=0,
debug_label=name or "comm",
)
return gemm_time, reduction_time, total_bytes
def get_embedding_f(self, vocab_size, seq_len, hidden_dim):
"""
Calculates the total time required for embedding operations, including computation and data transfer.
"""
batch = self._effective_transformer_batch()
embedding_mem = vocab_size * hidden_dim * self.precision.activations + seq_len * batch * hidden_dim * self.precision.activations
embedding_time = self.roofline(
0,
embedding_mem,
name="embedding_f",
mem_level=self.num_levels - 1,
) + self.O
if self.h2d_bandwidth and self.h2d_bandwidth > 0:
embedding_transfer_time = embedding_mem / self.h2d_bandwidth
else:
embedding_transfer_time = 0.0
if self.debug:
print(
"Embedding_mem: {:,}, transfer_time: {:.6f}".format(
int(embedding_mem / 1e9), embedding_transfer_time
)
)
return embedding_time + embedding_transfer_time, embedding_mem
def get_linear_softmax_f(self, gemm):
"""Estimate time for final projection + softmax forward.
assuming linear softmax gemm always use tensor parallelism sharded by vocab dimension
"""
_, effective_m, k, n = self._effective_dims(gemm)
gemm_time, reduction_time, size_bytes,_,_ = self._tensor_parallelism_gemm_forward(gemm, "linear_softmax_f", gemm_type=GemmType.LINEAR_SOFTMAX)
elements = effective_m * n / (self.tp * self.cp) # each tp-cp group holds a shard of the vocab dimension
point_flop = elements * SOFTMAX_FORWARD_FLOPS_PER_ELEMENT
# TODO:
# unsure if should be precision.activations or precision.stats
point_mem = self.precision.activations * elements * SOFTMAX_FORWARD_MEM_ACCESSES
point_time = self.roofline(
point_flop,
point_mem,
name="pointwise-linear-softmax-f",
mem_level=self.num_levels - 1,
) + 4 * self.O
if self.debug:
print(
"Linear Softmax (f) point_flop: {:,}, point_mem: {:,}".format(
int(point_flop / 1e9), int(point_mem / 1e9)
)
)
print("Linear Softmax (f) point_time: {:,}\n".format(point_time))
return gemm_time + reduction_time + point_time, point_mem
def get_linear_softmax_b(self, gemm):
_, effective_m, k, n = self._effective_dims(gemm)
gemm_time, reduction_time, size_bytes = self._tensor_parallelism_gemm_backward(gemm, "linear_softmax_b", gemm_type=GemmType.LINEAR_SOFTMAX)
elements = effective_m * n / (self.tp * self.cp) # each tp-cp group holds a shard of the vocab dimension
point_flop = elements * SOFTMAX_BACKWARD_FLOPS_PER_ELEMENT
# TODO:
# same here, unsure if should be precision.activations or precision.stats
point_mem = self.precision.activations * elements * SOFTMAX_BACKWARD_MEM_ACCESSES
point_time = self.roofline(
point_flop,
point_mem,
name="pointwise-linear-softmax-b",
mem_level=self.num_levels - 1,
) + 4 * self.O
if self.debug:
print(
"Linear Softmax (b) point_flop: {:,}, point_mem: {:,}".format(
int(point_flop / 1e9), int(point_mem / 1e9)
)
)
print("Linear Softmax (b) point_time: {:,}\n".format(point_time))
return gemm_time + reduction_time + point_time
def get_scale_softmax_f(self, gemm):
"""
Estimate time for scale + softmax forward.
total elements is divided by tp and cp since in tp-cp hybrid parallelism,
"""
batch, m, _, n = self._expand_gemm_descriptor(gemm)
elements = math.ceil(batch * m * n / (self.tp * self.cp))
flops = elements * (SOFTMAX_FORWARD_FLOPS_PER_ELEMENT + 1) # +1 for scaling
mem = self.precision.activations * elements * (SOFTMAX_FORWARD_MEM_ACCESSES )
time = self.roofline(
flops,
mem,
name="pointwise-scale-softmax-f",
mem_level=self.num_levels - 1,
) + self.O
return time
def get_scale_softmax_b(self, gemm):
batch, m, _, n = self._expand_gemm_descriptor(gemm)
elements = math.ceil(batch * m * n / (self.tp * self.cp))
flops = elements * (SOFTMAX_BACKWARD_FLOPS_PER_ELEMENT + 1) # +1 for scaling
mem = self.precision.activations * elements * (SOFTMAX_BACKWARD_MEM_ACCESSES)
time = self.roofline(
flops,
mem,
name="pointwise-scale_softmax-b",
mem_level=self.num_levels - 1,
) + self.O
if self.debug:
print(
"Scale Softmax (b) flop: {:,}, mem: {:,}".format(
int(flops / 1e9), int(mem / 1e9)
)
)
print("Scale Softmax(b) time: {:,}".format(time))
return time
def get_residual_f(self, tensor_shape):
# Residual operates on full tensor, not just GEMM output dimension
# TODO: double check!
batch, m, _, n = self._expand_gemm_descriptor(tensor_shape)
elements = batch * m * n
flops = 2 * elements # add + bias
mem = self.precision.activations * elements * 3 # read main, read residual, write out
time = self.roofline(flops, mem, name="pointwise-residual-f", mem_level=self.num_levels - 1) + self.O
if self.debug:
print(
"Residual (f) elements: {:,}, flops: {:,}, mem: {:,}".format(
int(elements / 1e6), int(flops / 1e9), int(mem / 1e9)
)
)
print("Residual (f) time: {:,}\n".format(time))
return time
def get_residual_b(self, tensor_shape):
# Residual operates on full tensor, not just GEMM output dimension
# TODO: double check!