Repository navigation
emulator: add an RTL-measured matrix accumulate latency model - #120
Shreyas8612 wants to merge 2 commits into
Conversation
Every matrix-matrix accumulate (M_MM/M_TMM/M_BMM/M_BTMM) charged
SYSTOLIC_PROCESSING_OVERHEAD + MLEN cycles, which cannot express the
BLEN-proportional cost of the RTL matrix pipeline, so the emulator could
not be run with the measured per-op timing.
Add TRANSACTIONAL.CONFIG.MATRIX_LATENCY_MODEL ("mlen" | "rtl_blen") with
a --matrix-latency-model CLI override. "rtl_blen" charges 3 * BLEN + 11
cycles per accumulate: Verilator runs of the RTL measured 23 cycles at
BLEN=4 and 35 at BLEN=8 (accumulates are serialized, each draining
before the next starts), which fixes the slope and the intercept. The
key is optional and defaults to "mlen", so existing settings files and
cycle counts are unchanged; matrix-vector ops and write-outs keep their
charges. The option and the measurements are documented in the readme.
Verified with cargo fmt, cargo clippy --workspace --all-targets
-D warnings and cargo test --workspace: new tests pin both formulas at
BLEN=4/8 with MLEN=64, check that switching the model changes only the
accumulate charge with identical results, and cover the settings
default/override resolution. The ATen linear testbench passes on the
default path, and re-running its program with --matrix-latency-model
rtl_blen leaves the VRAM contents identical while the cycle count moves.
There was a problem hiding this comment.
🔵 Needs a closer look
Validate invalid latency model values during configuration loading before approval.
Pull request overview
Adds configurable matrix accumulate latency models, including RTL-measured rtl_blen timing while preserving the existing default.
Changes:
- Adds TOML and CLI configuration for
mlenandrtl_blen. - Applies model-specific accumulate latency formulas.
- Documents and tests both models and defaults.
Review note: Invalid settings values can panic during initialization instead of using the existing configuration fallback.
File summaries
| File | Reviewed change |
|---|---|
transactional_emulator/src/runtime_config.rs |
Registers latency model configuration. |
transactional_emulator/src/runner.rs |
Applies CLI override and logs the model. |
transactional_emulator/src/matrix_machine.rs |
Charges model-specific accumulate cycles. |
transactional_emulator/src/load_config.rs |
Parses and resolves the latency model. |
transactional_emulator/src/cli.rs |
Adds --matrix-latency-model. |
transactional_emulator/readme.md |
Documents models and measurements. |
plena_settings.toml |
Sets the explicit default model. |
Review details
Suppressed comments (1)
transactional_emulator/src/load_config.rs:172
- An invalid
MATRIX_LATENCY_MODELvalue now panics whenMATRIX_LATENCY_MODELis first initialized, instead of being handled by the existingload_config().unwrap_or_else(...)fallback that warns and uses defaults. This makes a typo in an otherwise readable settings file abort the emulator duringMatrixMachine::new; validate the enum while loading the TOML and return/handle the configuration error consistently with the other settings.
Some(model) => model
.value
.parse()
.unwrap_or_else(|err| panic!("TRANSACTIONAL.CONFIG.MATRIX_LATENCY_MODEL: {err}")),
- Files reviewed: 7/7 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| /// Fixed part of the RTL-measured matrix-matrix accumulate cost, in cycles. | ||
| const RTL_ACCUMULATE_FIXED_CYCLES: u32 = 11; |
There was a problem hiding this comment.
I don’t understand why you need to do this. Why is it set to 11? Was that value obtained directly from the RTL? Plz send me the systolic-array version you’re trying to compare against?
There was a problem hiding this comment.
Thanks George. Yes, it comes directly from the RTL, and I've re-measured it on current main so it's reproducible.
Why it's needed. Every matrix-matrix accumulate in the emulator (M_MM, M_TMM, M_BMM, M_BTMM) charges SYSTOLIC_PROCESSING_OVERHEAD + MLEN cycles, with the overhead set to 0 in plena_settings.toml. On the RTL the accumulate cost doesn't depend on MLEN; it depends on BLEN. So the emulator under-charges at the RTL's default (MLEN 8: 8 vs 23 cycles) and over-charges at the emulator's default (MLEN 64: 64 vs 23). The option lets emulator timing match the RTL; the default stays mlen, so nothing changes unless you opt in.
Systolic array compared against. PLENA_RTL main @ 783ee48 (the open-source release): src/matrix_machine/rtl/matrix_machine.sv → src/basic_components/systolic_gemm_mxint/rtl/mxint_systolic_mcu.sv (KLEN = BLEN, MLEN/BLEN mini-arrays), default precision.svh, full SimTop under Verilator 5.034. Only MLEN/BLEN/HLEN/VLEN in configuration.svh were changed per geometry.
Method. DMA-free kernels through the normal rtl-sim flow (linear generator with --skip-asm-gen, then SimTop_tb.py): N back-to-back accumulates, one write-out, C_BREAK, for N = 8, 16, 24. The cost per accumulate is the slope of SimTop_tb's EXECUTION CLOCKS in N, so fixed overheads cancel. A control of N × S_ADDI_INT runs at 1 cycle per instruction, so fetch isn't the limit.
| MLEN | BLEN | RTL cycles per accumulate | 3·BLEN + 11 | Emulator today (mlen) |
|---|---|---|---|---|
| 8 | 4 | 23 | 23 | 8 |
| 16 | 4 | 23 | 23 | 16 |
| 32 | 4 | 23 | 23 | 32 |
| 64 | 4 | 23 | 23 | 64 |
| 16 | 8 | 35 | 35 | 16 |
| 32 | 8 | 35 | 35 | 32 |
| 32 | 16 | 59 | 59 | 32 |
M_MM, M_TMM, M_BMM and M_BTMM give identical numbers at every point, and stepping to fresh matrix/vector addresses on every accumulate changes nothing beyond the extra address instructions. So the cost is exactly linear in BLEN (slope 3, intercept 11) at three BLEN values and independent of MLEN from 8 to 64. The 11 is the measured intercept, not a tuned constant.
Emulator check. The same 24-accumulate kernel on the emulator at MLEN 64 / BLEN 4 costs 64 cycles per accumulate with mlen and 23 with this option, matching the RTL.
I've pushed 4c3ed11, which puts this provenance in the doc comments, the settings comment and the readme, and pins the BLEN 16 point in the unit test.
Reproduce
# PLENA_RTL @ 783ee48; set MLEN/BLEN/HLEN/VLEN in src/definitions/configuration.svh
# (features >= MLEN, batch a multiple of BLEN)
python -m tools.testworkloads.linear --build-dir build/test/kernel --batch 8 --in-features 32 --out-features 32
# replace build/test/kernel/generated_asm_code.asm with:
S_ADDI_INT gp1, gp0, 0
S_ADDI_INT gp2, gp0, 0
S_ADDI_INT gp3, gp0, 0
S_ADDI_INT gp4, gp0, <64 * VLEN>
M_MM 0, gp2, gp3 # repeated N times (or M_TMM / M_BMM / M_BTMM)
M_MM_WO gp4, gp0, 0 # M_BMM_WO for the batched ops
C_BREAK
python -m tools.testworkloads.linear --build-dir build/test/kernel --batch 8 --in-features 32 --out-features 32 --skip-asm-gen
python src/system/test/SimTop_tb.py --workload-dir build/test/kernel # with the usual rtl-sim env vars
# cycles per accumulate = (EXECUTION CLOCKS at N=24 - at N=8) / 16
| /// `3 * BLEN + 11`: measured on the RTL matrix pipeline with Verilator | ||
| /// (23 cycles at BLEN=4, 35 at BLEN=8). | ||
| #[value(name = "rtl_blen")] | ||
| RtlBlen, |
There was a problem hiding this comment.
What is the diff between BLEN and RtlBlen?
There was a problem hiding this comment.
There's no second BLEN: RtlBlen was only the name of the option ("RTL-measured, linear in BLEN") and it uses the normal TRANSACTIONAL.CONFIG.BLEN. Agreed the name read like a different parameter, so in 4c3ed11 I renamed it to rtl_measured (MatrixLatencyModel::RtlMeasured). The two values are now mlen (default, SYSTOLIC_PROCESSING_OVERHEAD + MLEN) and rtl_measured (3 · BLEN + 11).
…the measurement The option name rtl_blen read like a different BLEN. It is the RTL-measured accumulate model and uses the normal TRANSACTIONAL.CONFIG.BLEN, so call it rtl_measured (MatrixLatencyModel::RtlMeasured). Re-measured on PLENA_RTL 783ee48 with Verilator 5.034 (SimTop, DMA-free kernels of N back-to-back accumulates, cost = slope in N): 23 cycles at BLEN=4 for MLEN 8, 16, 32 and 64, 35 at BLEN=8 for MLEN 16 and 32, and 59 at BLEN=16 for MLEN 32, identical for M_MM, M_TMM, M_BMM and M_BTMM. That is exactly 3 * BLEN + 11 at three BLEN values, independent of MLEN. The doc comments, settings comment and readme now cite this measurement, and the unit test pins the BLEN=16 point. cargo fmt --check, clippy -D warnings and cargo test --workspace pass.
Every matrix-matrix accumulate (M_MM/M_TMM/M_BMM/M_BTMM) charged
SYSTOLIC_PROCESSING_OVERHEAD + MLEN cycles, which cannot express the
BLEN-proportional cost of the RTL matrix pipeline, so the emulator could
not be run with the measured per-op timing.
Add TRANSACTIONAL.CONFIG.MATRIX_LATENCY_MODEL ("mlen" | "rtl_blen") with
a --matrix-latency-model CLI override. "rtl_blen" charges 3 * BLEN + 11
cycles per accumulate: Verilator runs of the RTL measured 23 cycles at
BLEN=4 and 35 at BLEN=8 (accumulates are serialized, each draining
before the next starts), which fixes the slope and the intercept. The
key is optional and defaults to "mlen", so existing settings files and
cycle counts are unchanged; matrix-vector ops and write-outs keep their
charges. The option and the measurements are documented in the readme.
Verified with cargo fmt, cargo clippy --workspace --all-targets
-D warnings and cargo test --workspace: new tests pin both formulas at
BLEN=4/8 with MLEN=64, check that switching the model changes only the
accumulate charge with identical results, and cover the settings
default/override resolution. The ATen linear testbench passes on the
default path, and re-running its program with --matrix-latency-model
rtl_blen leaves the VRAM contents identical while the cycle count moves.