Skip to content

Commit ccc90aa

Browse files
romain-cpuHarmonyHu
authored andcommitted
feat: llm support fp8 analyse
- add fp8matmul calcs Change-Id: I6032b4cfc02c6b1f7f7239659bc7d0c5818bb3a8
1 parent e4a78f9 commit ccc90aa

2 files changed

Lines changed: 152 additions & 30 deletions

File tree

python/tools/llm_analyse.py

Lines changed: 79 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
e.g. qwen3.5-0.8b_bf16_seq2048_bm1684x_1dev_static/block_0/block_0.mlir
2424
2525
Usage:
26-
python llm_analyse.py <model_dir> -t 32 -b 64
27-
python llm_analyse.py <model_dir> -t 32 -b 64 -d w4f16 -o result.xlsx
26+
python llm_analyse.py -m <model> -c bm1684x -s 2048 -t 16 -b 64 -o <out_dir>
27+
# -t: FP16 TOPS; INT8/FP8 TOPS default to 2*t
2828
"""
2929

3030
import os
@@ -128,6 +128,24 @@ def discover_modules(model_dir: str,
128128
# Single-module analysis
129129
# ---------------------------------------------------------------------------
130130

131+
COMPUTE_FP16 = "fp16"
132+
COMPUTE_INT8 = "int8"
133+
COMPUTE_FP8 = "fp8"
134+
COMPUTE_VECTOR = "vector"
135+
136+
137+
def _compute_type(base_opn: str) -> str:
138+
"""Pick compute-power bucket for roofline estimate.
139+
140+
Fp8MatMul computes in FP8; weight-only quant (w8/w4 f16/bf16) still
141+
computes in FP16, so all other key ops use FP16 TOPS.
142+
"""
143+
if base_opn == "Fp8MatMul":
144+
return COMPUTE_FP8
145+
if base_opn in KEY_OPS:
146+
return COMPUTE_FP16
147+
return COMPUTE_VECTOR
148+
131149

132150
def analyse_module(filepath: str, dtype_mode: str = "f16"):
133151
"""Parse and analyse a single MLIR file.
@@ -145,6 +163,8 @@ def analyse_module(filepath: str, dtype_mode: str = "f16"):
145163
if opn == "MatMul" and ssa_op_map and len(op.operands) > 1:
146164
if ssa_op_map.get(op.operands[1], "") == "top.Weight":
147165
opn = f"MatMul ({dtype_mode})"
166+
elif opn == "Fp8MatMul":
167+
opn = f"Fp8MatMul ({dtype_mode})"
148168
flops = calc_flops(op)
149169
rb, wb = calc_data_volume(op, dtype_mode, ssa_op_map)
150170
inp_shapes = ", ".join(t.shape_str() if t else "none" for t in op.input_types)
@@ -162,6 +182,7 @@ def analyse_module(filepath: str, dtype_mode: str = "f16"):
162182
wb=wb,
163183
total_io=rb + wb,
164184
is_key=base_opn in KEY_OPS,
185+
compute_type=_compute_type(base_opn),
165186
))
166187
total_flops += flops
167188
total_read += rb
@@ -180,11 +201,13 @@ def analyse_module(filepath: str, dtype_mode: str = "f16"):
180201

181202

182203
def export_llm_excel(modules_data,
183-
chip_tops,
204+
fp16_tops,
184205
bw_gbps,
185206
out_dir,
186207
llm_path,
187208
dtype_mode="f16",
209+
int8_tops=None,
210+
fp8_tops=None,
188211
vector_tops=None,
189212
uarch_rate=0.8,
190213
bw_util=0.7,
@@ -211,8 +234,12 @@ def export_llm_excel(modules_data,
211234
from openpyxl.formatting.rule import CellIsRule
212235
from openpyxl.utils import get_column_letter
213236

237+
if int8_tops is None:
238+
int8_tops = fp16_tops * 2.0
239+
if fp8_tops is None:
240+
fp8_tops = fp16_tops * 2.0
214241
if vector_tops is None:
215-
vector_tops = chip_tops / 8.0
242+
vector_tops = fp16_tops / 8.0
216243

217244
# ---------- Color palette ----------
218245
C_TITLE = "1F4E79" # deep blue - main title
@@ -322,13 +349,15 @@ def _banner(ws, row, text, span, fill=section_fill, font=section_font, height=22
322349

323350
# params start at row 9
324351
params = [
325-
("Chip Compute Power", chip_tops, "TOPS", "#,##0.##"),
352+
("FP16 Compute Power", fp16_tops, "TOPS", "#,##0.##"),
353+
("INT8 Compute Power", int8_tops, "TOPS", "#,##0.##"),
354+
("FP8 Compute Power", fp8_tops, "TOPS", "#,##0.##"),
326355
("Vector Compute Power", vector_tops, "TOPS", "#,##0.##"),
327356
("Chip Bandwidth", bw_gbps, "GB/s", "#,##0.##"),
328357
("uArch Rate", uarch_rate, "", "0%"),
329358
("Bandwidth Utilization", bw_util, "", "0%"),
330359
("Parallelism", parallelism, "", "0%"),
331-
("Serialism", "=1-B14", "", "0%"),
360+
("Serialism", "=1-B16", "", "0%"),
332361
("CPU Call", 100, "us", "#,##0"),
333362
("Preprocess Time", 0.1, "s", "#,##0.000"),
334363
]
@@ -351,14 +380,26 @@ def _banner(ws, row, text, span, fill=section_fill, font=section_font, height=22
351380
uc.alignment = center
352381

353382
# Formula references (must match absolute positions above)
354-
TOPS_REF = "Overview!$B$9"
355-
VECTOR_TOPS_REF = "Overview!$B$10"
356-
BW_REF = "Overview!$B$11"
357-
CU_REF = "Overview!$B$12"
358-
BU_REF = "Overview!$B$13"
359-
PAR_REF = "Overview!$B$14"
360-
CPU_CALL_REF = "Overview!$B$16"
361-
PREPROCESS_TIME_REF = "Overview!$B$17"
383+
FP16_TOPS_REF = "Overview!$B$9"
384+
INT8_TOPS_REF = "Overview!$B$10"
385+
FP8_TOPS_REF = "Overview!$B$11"
386+
VECTOR_TOPS_REF = "Overview!$B$12"
387+
BW_REF = "Overview!$B$13"
388+
CU_REF = "Overview!$B$14"
389+
BU_REF = "Overview!$B$15"
390+
PAR_REF = "Overview!$B$16"
391+
CPU_CALL_REF = "Overview!$B$18"
392+
PREPROCESS_TIME_REF = "Overview!$B$19"
393+
394+
def _tops_ref_for_op(d):
395+
ctype = d.get("compute_type", COMPUTE_VECTOR)
396+
if ctype == COMPUTE_INT8:
397+
return INT8_TOPS_REF
398+
if ctype == COMPUTE_FP8:
399+
return FP8_TOPS_REF
400+
if ctype == COMPUTE_FP16:
401+
return FP16_TOPS_REF
402+
return VECTOR_TOPS_REF
362403

363404
# --- Special Ratios (rows 19+) ---
364405
RATIO_BANNER = PARAM_START + len(params) + 1 # 19
@@ -409,7 +450,7 @@ def _banner(ws, row, text, span, fill=section_fill, font=section_font, height=22
409450
cell.alignment = center
410451
cell.border = thin
411452

412-
def _compute_formula(gops_cell, tops_ref=TOPS_REF):
453+
def _compute_formula(gops_cell, tops_ref=FP16_TOPS_REF):
413454
return f"=IF({tops_ref}=0,0,{gops_cell}/({tops_ref}*{CU_REF})*1000)"
414455

415456
def _memory_formula(io_cell):
@@ -473,7 +514,7 @@ def _memory_formula(io_cell):
473514
elif c in (5, 6, 7):
474515
cell.number_format = "#,##0.000"
475516

476-
tops_ref = TOPS_REF if d["is_key"] else VECTOR_TOPS_REF
517+
tops_ref = _tops_ref_for_op(d)
477518
# H: Compute(us) - from GOPs column D
478519
cell_h = ws.cell(row=r, column=8)
479520
cell_h.value = _compute_formula(f"D{r}", tops_ref)
@@ -629,14 +670,16 @@ def _memory_formula(io_cell):
629670
# ============ Back-fill TTFT / Tokens/s at top (rows 4, 5) ============
630671
# TTFT row (4)
631672
ws0.cell(row=4, column=2, value=f"=F{phase_r}").number_format = "#,##0.000000"
632-
ws0.cell(row=4, column=4, value=f"=IF(F{phase_r}=0,0,C{phase_r}/{TOPS_REF}/1000/F{phase_r})")
673+
ws0.cell(row=4,
674+
column=4,
675+
value=f"=IF(F{phase_r}=0,0,C{phase_r}/{FP16_TOPS_REF}/1000/F{phase_r})")
633676
ws0.cell(row=4, column=6, value=f"=IF(F{phase_r}=0,0,D{phase_r}/{BW_REF}/1000/F{phase_r})")
634677
# Tokens/s row (5)
635678
ws0.cell(row=5, column=2,
636679
value=f"=IF(F{phase_r+1}=0,0,1/F{phase_r+1})").number_format = "#,##0.00"
637680
ws0.cell(row=5,
638681
column=4,
639-
value=f"=IF(F{phase_r+1}=0,0,C{phase_r+1}/{TOPS_REF}/1000/F{phase_r+1})")
682+
value=f"=IF(F{phase_r+1}=0,0,C{phase_r+1}/{FP16_TOPS_REF}/1000/F{phase_r+1})")
640683
ws0.cell(row=5,
641684
column=6,
642685
value=f"=IF(F{phase_r+1}=0,0,D{phase_r+1}/{BW_REF}/1000/F{phase_r+1})")
@@ -694,7 +737,7 @@ def _memory_formula(io_cell):
694737
"Green cells (parameters, ratios, block counts) are editable; all estimates auto-update.",
695738
"Est.Time = max(Compute, Memory) + Serialism * min(Compute, Memory).",
696739
"block / block_cache use the first block as representative, multiplied by Count.",
697-
"Key operators are highlighted in yellow; use chip TOPS (else vector TOPS) for compute.",
740+
"Key operators: Fp8MatMul uses FP8 TOPS; others (incl. w8/w4 MatMul) use FP16 TOPS; non-key ops use vector TOPS.",
698741
]
699742
for i, txt in enumerate(notes, 1):
700743
ws0.merge_cells(start_row=info_banner + i,
@@ -728,12 +771,19 @@ def main():
728771
parser.add_argument('-s', '--seq_length', type=int, required=True,
729772
help="sequence length")
730773
parser.add_argument("-t", "--tops", type=float, required=True,
731-
help="Chip compute power in TOPS")
774+
help="FP16 compute power in TOPS")
775+
parser.add_argument("--int8_tops", type=float, default=None,
776+
help="INT8 compute power in TOPS (default: 2 * tops)")
777+
parser.add_argument("--fp8_tops", type=float, default=None,
778+
help="FP8 compute power in TOPS (default: 2 * tops)")
732779
parser.add_argument("-b", "--bandwidth", type=float, required=True,
733780
help="Chip memory bandwidth in GB/s")
734781
parser.add_argument("-q", "--quantize", default="f16",
735782
choices=["f16", "w8f16", "w4f16","bf16", "w8bf16", "w4bf16"],
736783
help="Quantization mode (default: f16)")
784+
parser.add_argument("-c", "--chip", default="bm1684x",
785+
choices=["bm1684x", "bm1688", "cv186x", "bm1690", "bm1684x2"],
786+
help="Chip type (default: bm1684x)")
737787
parser.add_argument("-v", "--vector_tops", type=float, default=None,
738788
help="Vector compute power in TOPS (default: tops/8)")
739789
parser.add_argument("-r", "--uarch_rate", type=float, default=0.8,
@@ -768,7 +818,7 @@ def main():
768818
max_pixels = "768,768"
769819
cmds = [
770820
"llm_convert.py", f"-m {args.model_path}", f"-s {args.seq_length}", f"-q {args.quantize}",
771-
"-c bm1684x", f"--out_dir {args.out_dir}", "--only_mlir", f"--max_pixels {max_pixels}"
821+
f"-c {args.chip}", f"--out_dir {args.out_dir}", "--only_mlir", f"--max_pixels {max_pixels}"
772822
]
773823
if args.max_input_length > 0:
774824
cmds.append(f"--max_input_length {args.max_input_length}")
@@ -821,15 +871,19 @@ def main():
821871
modules_data = []
822872
for name, path, count in modules:
823873
print(f" Analysing: {name} ({os.path.basename(path)})")
824-
dtype = args.quantize if name.startswith("block") else "f16"
874+
dtype = args.quantize if name.startswith("block") else args.quantize.replace("fp8", "")
825875
rows_data, totals = analyse_module(path, dtype)
826876
modules_data.append((name, count, rows_data, totals))
827877

828878
# Step 3: Export Excel
829879
cmdline = "python " + " ".join(sys.argv)
830-
export_llm_excel(modules_data, args.tops, args.bandwidth, args.out_dir, args.model_path,
831-
args.quantize, args.vector_tops, args.uarch_rate, args.bw_util,
832-
args.parallelism, model_config, args.seq_length, max_pixels, cmdline)
880+
fp16_tops = args.tops
881+
int8_tops = args.int8_tops if args.int8_tops is not None else fp16_tops * 2.0
882+
fp8_tops = args.fp8_tops if args.fp8_tops is not None else fp16_tops * 2.0
883+
export_llm_excel(modules_data, fp16_tops, args.bandwidth, args.out_dir, args.model_path,
884+
args.quantize, int8_tops, fp8_tops, args.vector_tops, args.uarch_rate,
885+
args.bw_util, args.parallelism, model_config, args.seq_length, max_pixels,
886+
cmdline)
833887

834888

835889
if __name__ == "__main__":

python/tools/mlir_analyse.py

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,15 @@ def element_size(self) -> int:
4343
sizes = {
4444
"f32": 4,
4545
"f16": 2,
46-
"f16": 2,
46+
"bf16": 2,
4747
"f64": 8,
48+
"f8E4M3FN": 1,
49+
"f8E5M2": 1,
4850
"i8": 1,
4951
"i16": 2,
5052
"i32": 4,
5153
"i64": 8,
54+
"si32": 4,
5255
"ui8": 1,
5356
"ui16": 2,
5457
"ui32": 4,
@@ -284,6 +287,34 @@ def calc_matmul_flops(op: MLIROp) -> int:
284287
return 2 * batch * M * K * N
285288

286289

290+
def calc_fp8matmul_flops(op: MLIROp) -> int:
291+
"""FP8 block-wise quantized MatMul.
292+
293+
Computation phases:
294+
1. Dynamic quantization per block: 2 * M * K
295+
2. MatMul and rescale: 4 * batch * M * K * N
296+
"""
297+
vi = _valid_inputs(op)
298+
if len(vi) < 2:
299+
return 0
300+
lhs = vi[0]
301+
out = op.output_types[0] if op.output_types else None
302+
if not out:
303+
raise ValueError(f"Fp8MatMul op at line {op.line_num} has no output type")
304+
305+
# Determine effective K, N from lhs shape
306+
K = lhs.shape[-1]
307+
M = out.shape[-2]
308+
N = out.shape[-1]
309+
batch = 1
310+
for d in out.shape[:-2]:
311+
batch *= d
312+
313+
matmul_flops = 4 * batch * M * K * N
314+
dynamic_quantize_flops = 2 * M * K
315+
return matmul_flops + dynamic_quantize_flops
316+
317+
287318
def calc_conv_flops(op: MLIROp) -> int:
288319
vi = _valid_inputs(op)
289320
if len(vi) < 2:
@@ -407,6 +438,10 @@ def calc_flops(op: MLIROp) -> int:
407438
# calc_matmul_flops uses lhs last dim as K and rhs[-2] as N (right_transpose),
408439
# which is correct for both w4 and w8.
409440
return calc_matmul_flops(op)
441+
elif name == "Fp8MatMul":
442+
# FP8 block-wise quantized MatMul.
443+
# Includes matmul FLOPs + weight dequantization overhead.
444+
return calc_fp8matmul_flops(op)
410445
elif name == "Conv":
411446
return calc_conv_flops(op)
412447
elif name == "FAttention":
@@ -545,6 +580,10 @@ def calc_flops(op: MLIROp) -> int:
545580
# Bytes per element for each quantization type
546581
DTYPE_BYTES = {
547582
"f16": 2,
583+
"bf16": 2,
584+
"f32": 4,
585+
"f8E4M3FN": 1,
586+
"f8E5M2": 1,
548587
"w4": 0.5,
549588
"w8": 1,
550589
}
@@ -561,8 +600,10 @@ def _get_weight_bytes(dtype_mode: str) -> float:
561600
return DTYPE_BYTES["w4"]
562601
elif dtype_mode in ["w8f16", "w8bf16"]:
563602
return DTYPE_BYTES["w8"]
564-
else: # f16
565-
return DTYPE_BYTES["f16"]
603+
elif dtype_mode in ["f8E4M3FN", "f8E5M2", "bf16", "f16"]:
604+
return DTYPE_BYTES[dtype_mode]
605+
else:
606+
raise ValueError(f"Unsupported dtype mode: {dtype_mode}")
566607

567608

568609
def calc_data_volume(op: MLIROp,
@@ -609,6 +650,31 @@ def calc_data_volume(op: MLIROp,
609650
rb += _tensor_bytes(t, act_bytes)
610651
else:
611652
rb += t.size_bytes
653+
elif opn == "Fp8MatMul":
654+
# Inputs: [activation, fp8_weight, scale_inv, none]
655+
# - activation: use f16/bf16/f32 (activation bytes).
656+
# - fp8_weight: f8E4M3FN, 1 byte per element (from top.Weight).
657+
# - scale_inv: typically f32, 4 bytes per element (from top.Weight).
658+
# - none: skip.
659+
for idx, t in enumerate(op.input_types):
660+
if not isinstance(t, TensorInfo):
661+
continue
662+
if idx == 0:
663+
rb += _tensor_bytes(t, act_bytes)
664+
elif idx == 1:
665+
# fp8 weight - use actual storage size (f8E4M3FN = 1 byte/elem)
666+
is_weight = False
667+
if ssa_op_map and len(op.operands) > 1:
668+
is_weight = ssa_op_map.get(op.operands[1], "") == "top.Weight"
669+
if is_weight:
670+
rb += t.size_bytes
671+
else:
672+
rb += _tensor_bytes(t, act_bytes)
673+
elif idx == 2:
674+
# scale_inv - use actual dtype size (typically f32)
675+
rb += t.size_bytes
676+
else:
677+
rb += _tensor_bytes(t, act_bytes)
612678
else:
613679
for idx, t in enumerate(op.input_types):
614680
if not isinstance(t, TensorInfo):
@@ -663,7 +729,7 @@ def fmt_time(us: float) -> str:
663729
# Excel export
664730
# ---------------------------------------------------------------------------
665731

666-
KEY_OPS = {"MatMul", "Conv", "FAttention", "ChunkGatedDeltaRule", "A16MatMul"}
732+
KEY_OPS = {"MatMul", "Conv", "FAttention", "ChunkGatedDeltaRule", "A16MatMul", "Fp8MatMul"}
667733
SKIP_OPS = {"top.Weight", "top.None", "top.Input"}
668734

669735

@@ -706,6 +772,8 @@ def export_excel(ops: List[MLIROp],
706772
elif opn == "A16MatMul":
707773
wbits = _parse_int_attr(op.attributes.get("weight_bits", "4"), 4)
708774
opn = f"A16MatMul (w{wbits}a16)"
775+
elif opn == "Fp8MatMul":
776+
opn = f"Fp8MatMul ({dtype_mode})"
709777
flops = calc_flops(op)
710778
rb, wb = calc_data_volume(op, dtype_mode, ssa_op_map)
711779
total_io = rb + wb
@@ -799,7 +867,7 @@ def _set_col_widths(ws, widths):
799867
info_row = 9
800868
static_items = [
801869
("MLIR File", os.path.basename(output_path).replace("_analysis.xlsx", ".mlir")),
802-
("Dtye Mode", dtype_mode),
870+
("Dtype Mode", dtype_mode),
803871
("Activation", "f16 (2 bytes)"),
804872
("", ""),
805873
("Total GOPs", total_flops / 1e9),

0 commit comments

Comments
 (0)