From 621c639a9e40472b86e66704a6e4aba9cfc93846 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Wed, 7 Oct 2026 05:47:39 +0200 Subject: [PATCH 1/2] Encode result block scales as OCP E8M0 The result quantizer emitted signed two's complement block scales while MXCore operands, OCP MX and iDMA use unsigned E8M0 (bias 127), so MXCore could not consume its own MXFP8 results. Emit E8M0 scales, clamped at 0, and an E8M0 NaN scale (0xFF) for poisoned blocks. Update PyGolden to match. --- PyGolden/mxcore_gemm_functions.py | 4 +++- PyGolden/mxcore_utils.py | 14 +++----------- mxcore-rtl/src/hwpe/block_scale.sv | 17 ++++------------- mxcore-rtl/src/hwpe/fp32_to_mxfp8_quantizer.sv | 2 +- .../src/hwpe/mxcore_hwpe_result_quantizer.sv | 2 +- 5 files changed, 12 insertions(+), 27 deletions(-) diff --git a/PyGolden/mxcore_gemm_functions.py b/PyGolden/mxcore_gemm_functions.py index 056809b..5e20d6d 100644 --- a/PyGolden/mxcore_gemm_functions.py +++ b/PyGolden/mxcore_gemm_functions.py @@ -309,6 +309,7 @@ def pack_uint8_to_lines(byte_list): nan_byte = 0x7D if data_type != "FP8ALT" else 0x7F if poisoned and block_poison_enable: quantized_all = [nan_byte] * len(block_fp32) + scale = 0xFF else: quantized_all = [quantize_fn(v, scale) for v in block_fp32] mx_row_segments.append((quantized_all[:q_cols], scale)) @@ -498,14 +499,15 @@ def write_to_c_header(fp9_matrix_a, fp9_matrix_b, scale_matrix_a, scale_matrix_b segment = [_acc_to_fp32_u32(result_matrix[row_idx][col_start + q_start + j]) for j in range(q_cols)] block_fp32 = segment + [0] * (result_mx_block_size - q_cols) scale, poisoned = block_scale(block_fp32, data_type="FP8ALT" if data_type == "FP8ALT" else "FP8") - result_scale_mx.append(scale) quantize_fn = quantize_e5m2 if data_type != "FP8ALT" else quantize_e4m3 nan_byte = 0x7D if data_type != "FP8ALT" else 0x7F if poisoned and block_poison_enable: quantized_all = [nan_byte] * len(block_fp32) + scale = 0xFF else: quantized_all = [quantize_fn(v, scale) for v in block_fp32] result_mx_all.extend(quantized_all[:q_cols]) + result_scale_mx.append(scale) # Pack bytes into memory_data_width words bytes_per_word = memory_data_width // 8 diff --git a/PyGolden/mxcore_utils.py b/PyGolden/mxcore_utils.py index 5f4e18e..6c4449a 100644 --- a/PyGolden/mxcore_utils.py +++ b/PyGolden/mxcore_utils.py @@ -18,9 +18,6 @@ def bin32_to_float(s32): # "01000000101000000000000000000000" -> 5.0 b = int(s32[-32:], 2) return struct.unpack('!f', struct.pack('!I', b))[0] -def to_signed8(x): - return x - 256 if x > 127 else x - def vec_to_hex(vec, vector_size=8, memory_data_width=32): """ Converts a vector of binary strings into hex memory lines (padded mode). @@ -178,13 +175,8 @@ def block_scale(block, data_type="FP8"): emax = 15 if data_type == "FP8" else 8 max_exp = max([(fp32_to_fields(x)[1]) for x in block]) is_special = any((fp32_to_fields(x)[1] == 0xFF) for x in block) - scaled = max_exp - (127 + emax) - scale_emax = 127 - if scaled > scale_emax: - return scale_emax & 0xFF, True - if scaled < -scale_emax: - return (-scale_emax) & 0xFF, is_special - return scaled & 0xFF, is_special + # OCP E8M0 scale (bias 127) + return max(max_exp - emax, 0), is_special def _quantize_mxfp8(fp32_val, scale, mant_bits, exp_bits, bias, exp_max, exp_min, sat_byte, nan_byte, full_field_reserved): sign, exp, mant = fp32_to_fields(fp32_val) @@ -199,7 +191,7 @@ def _quantize_mxfp8(fp32_val, scale, mant_bits, exp_bits, bias, exp_max, exp_min is_subnorm_in = (exp == 0) unbiased_exp = (1 - 127) if is_subnorm_in else (exp - 127) full_mant = mant if is_subnorm_in else ((1 << 23) | mant) - scaled_exp = unbiased_exp - to_signed8(scale) + scaled_exp = unbiased_exp - (scale - 127) if scaled_exp > exp_max: return (sign << 7) | sat_byte diff --git a/mxcore-rtl/src/hwpe/block_scale.sv b/mxcore-rtl/src/hwpe/block_scale.sv index b0b0379..f5ede4c 100644 --- a/mxcore-rtl/src/hwpe/block_scale.sv +++ b/mxcore-rtl/src/hwpe/block_scale.sv @@ -19,13 +19,10 @@ module block_scale #( output logic block_poison_o ); - localparam int FP32_BIAS = 127; localparam int E5M2_EMAX = 15; localparam int E4M3_EMAX = 8; localparam int MXFP8_EMAX = (Encoding == 0) ? E5M2_EMAX : E4M3_EMAX; - localparam int SCALE_EMAX = (2 ** (ScaleWidth-1)) - 1; - // Number of Levels/Stages in the Comparator Tree localparam int STAGES = $clog2(BlockSize); @@ -41,8 +38,6 @@ module block_scale #( // Scaled Exponent logic signed [9:0] scaled_exponent; - logic scale_overflow; - logic scale_underflow; // Comparator Tree Stages logic [STAGES:0][BlockSize-1:0][7:0] stage; @@ -71,14 +66,10 @@ module block_scale #( endgenerate assign max_exponent = stage[STAGES][0]; - assign scaled_exponent = $signed({2'b00, max_exponent}) - FP32_BIAS - MXFP8_EMAX; - assign scale_overflow = scaled_exponent > SCALE_EMAX; - assign scale_underflow = scaled_exponent < -SCALE_EMAX; - - assign block_scale_o = scale_overflow ? ScaleWidth'(SCALE_EMAX) : - scale_underflow ? ScaleWidth'(-SCALE_EMAX) : - scaled_exponent[ScaleWidth-1:0]; + // OCP E8M0 Scale (Bias 127) + assign scaled_exponent = $signed({2'b00, max_exponent}) - MXFP8_EMAX; + assign block_scale_o = (scaled_exponent < 0) ? '0 : scaled_exponent[ScaleWidth-1:0]; - assign block_poison_o = scale_overflow || (|is_special); + assign block_poison_o = |is_special; endmodule : block_scale diff --git a/mxcore-rtl/src/hwpe/fp32_to_mxfp8_quantizer.sv b/mxcore-rtl/src/hwpe/fp32_to_mxfp8_quantizer.sv index 424c30b..7d0f7fb 100644 --- a/mxcore-rtl/src/hwpe/fp32_to_mxfp8_quantizer.sv +++ b/mxcore-rtl/src/hwpe/fp32_to_mxfp8_quantizer.sv @@ -84,7 +84,7 @@ module fp32_to_mxfp8_quantizer #( end else begin unbiased_exponent = is_input_subnorm ? (1 - FP32_BIAS) : (fp32_exponent - FP32_BIAS); full_mantissa = is_input_subnorm ? {1'b0, fp32_mantissa} : {1'b1, fp32_mantissa}; - scaled_exponent = unbiased_exponent - $signed(scale_i); + scaled_exponent = unbiased_exponent - ($signed({2'b00, scale_i}) - FP32_BIAS); // Overflow if (scaled_exponent > EXPONENT_MAX) begin mxfp8_exponent = (Encoding == 0) ? ((Saturate == 1) ? 5'h1e : 5'h1f) : 4'hf; diff --git a/mxcore-rtl/src/hwpe/mxcore_hwpe_result_quantizer.sv b/mxcore-rtl/src/hwpe/mxcore_hwpe_result_quantizer.sv index 5373b4a..5f546de 100644 --- a/mxcore-rtl/src/hwpe/mxcore_hwpe_result_quantizer.sv +++ b/mxcore-rtl/src/hwpe/mxcore_hwpe_result_quantizer.sv @@ -77,7 +77,7 @@ module mxcore_hwpe_result_quantizer #( assign mxfp8_result_o.strb = {(OutputDataWidth/8){1'b1}}; assign mx_result_scale_o.valid = fp32_result_i.valid; - assign mx_result_scale_o.data = block_scale; + assign mx_result_scale_o.data = (block_poison && block_poison_i) ? '1 : block_scale; assign mx_result_scale_o.strb = {ScaleWidth/8{1'b1}}; endmodule : mxcore_hwpe_result_quantizer From be345c069627487ce5637febae8f3f9ba8cebd5d Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Wed, 7 Oct 2026 05:59:57 +0200 Subject: [PATCH 2/2] Drop a comment on the result scale encoding --- PyGolden/mxcore_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/PyGolden/mxcore_utils.py b/PyGolden/mxcore_utils.py index 6c4449a..6097726 100644 --- a/PyGolden/mxcore_utils.py +++ b/PyGolden/mxcore_utils.py @@ -175,7 +175,6 @@ def block_scale(block, data_type="FP8"): emax = 15 if data_type == "FP8" else 8 max_exp = max([(fp32_to_fields(x)[1]) for x in block]) is_special = any((fp32_to_fields(x)[1] == 0xFF) for x in block) - # OCP E8M0 scale (bias 127) return max(max_exp - emax, 0), is_special def _quantize_mxfp8(fp32_val, scale, mant_bits, exp_bits, bias, exp_max, exp_min, sat_byte, nan_byte, full_field_reserved):