Generalize block filter in Adapter (#3133)

As per @Schaeff's comment
https://github.com/powdr-labs/powdr/pull/3124#issuecomment-3144116469.

This can be orthogonal with #3124, which this PR can apply on top of.
Alternatively, if we don't want the CLI option of max number of basic
block instructions, we can apply this PR without #3124.
This commit is contained in:
Steve Wang
2025-08-04 15:55:51 +08:00
committed by GitHub
parent a03c1960ea
commit 4994f1aa68
5 changed files with 36 additions and 59 deletions

View File

@@ -6,7 +6,7 @@ use powdr_number::FieldElement;
use serde::{Deserialize, Serialize};
use crate::{
blocks::{Candidate, Instruction, Program},
blocks::{BasicBlock, Candidate, Instruction, Program},
constraint_optimizer::IsBusStateful,
memory_optimizer::MemoryBusInteraction,
Apc, InstructionHandler, VmConfig,
@@ -32,6 +32,10 @@ pub trait Adapter: Sized {
fn into_field(e: Self::PowdrField) -> Self::Field;
fn from_field(e: Self::Field) -> Self::PowdrField;
fn should_skip_block(_block: &BasicBlock<Self::Instruction>) -> bool {
false
}
}
pub type ApcStats<A> = <<A as Adapter>::Candidate as Candidate<A>>::ApcStats;

View File

@@ -23,8 +23,7 @@ pub enum PgoConfig {
/// value = cells saved per apc * times executed
/// cost = number of columns in the apc
/// constraint of max total columns
/// constraint of max number of instructions per block
Cell(HashMap<u64, u32>, Option<usize>, Option<usize>),
Cell(HashMap<u64, u32>, Option<usize>),
/// value = instruction per apc * times executed
Instruction(HashMap<u64, u32>),
/// value = instruction per apc
@@ -36,7 +35,7 @@ impl PgoConfig {
/// Returns the number of times a certain pc was executed in the profile.
pub fn pc_execution_count(&self, pc: u64) -> Option<u32> {
match self {
PgoConfig::Cell(pc_count, _, _) | PgoConfig::Instruction(pc_count) => {
PgoConfig::Cell(pc_count, _) | PgoConfig::Instruction(pc_count) => {
pc_count.get(&pc).copied()
}
PgoConfig::None => None,
@@ -60,11 +59,10 @@ pub enum PgoType {
pub fn pgo_config(
pgo: PgoType,
max_columns: Option<usize>,
max_block_instructions: Option<usize>,
execution_profile: HashMap<u64, u32>,
) -> PgoConfig {
match pgo {
PgoType::Cell => PgoConfig::Cell(execution_profile, max_columns, max_block_instructions),
PgoType::Cell => PgoConfig::Cell(execution_profile, max_columns),
PgoType::Instruction => PgoConfig::Instruction(execution_profile),
PgoType::None => PgoConfig::None,
}
@@ -118,7 +116,6 @@ fn create_apcs_with_cell_pgo<A: Adapter>(
pgo_program_pc_count: HashMap<u64, u32>,
config: &PowdrConfig,
max_total_apc_columns: Option<usize>,
max_block_instructions: Option<usize>,
vm_config: AdapterVmConfig<A>,
) -> Vec<(AdapterApc<A>, ApcStats<A>)> {
if config.autoprecompiles == 0 {
@@ -130,11 +127,6 @@ fn create_apcs_with_cell_pgo<A: Adapter>(
// Also only keep basic blocks with more than one original instruction.
blocks.retain(|b| pgo_program_pc_count.contains_key(&b.start_pc) && b.statements.len() > 1);
// drop any block with more than max_block_instructions instructions
if let Some(max_block_instructions) = max_block_instructions {
blocks.retain(|b| b.statements.len() <= max_block_instructions);
}
tracing::debug!(
"Retained {} basic blocks after filtering by pc_idx_count",
blocks.len()
@@ -247,30 +239,30 @@ fn create_apcs_with_no_pgo<A: Adapter>(
}
pub fn generate_apcs_with_pgo<A: Adapter>(
blocks: Vec<BasicBlock<A::Instruction>>,
mut blocks: Vec<BasicBlock<A::Instruction>>,
config: &PowdrConfig,
max_total_apc_columns: Option<usize>,
pgo_config: PgoConfig,
vm_config: AdapterVmConfig<A>,
) -> Vec<(AdapterApc<A>, Option<ApcStats<A>>)> {
// filter out blocks that should be skipped according to the adapter
blocks.retain(|block| !A::should_skip_block(block));
// sort basic blocks by:
// 1. if PgoConfig::Cell, cost = frequency * cells_saved_per_row
// 2. if PgoConfig::Instruction, cost = frequency * number_of_instructions
// 3. if PgoConfig::None, cost = number_of_instructions
let res: Vec<_> = match pgo_config {
PgoConfig::Cell(pgo_program_idx_count, _, max_block_instructions) => {
create_apcs_with_cell_pgo::<A>(
blocks,
pgo_program_idx_count,
config,
max_total_apc_columns,
max_block_instructions,
vm_config,
)
.into_iter()
.map(|(apc, apc_stats)| (apc, Some(apc_stats)))
.collect()
}
PgoConfig::Cell(pgo_program_idx_count, _) => create_apcs_with_cell_pgo::<A>(
blocks,
pgo_program_idx_count,
config,
max_total_apc_columns,
vm_config,
)
.into_iter()
.map(|(apc, apc_stats)| (apc, Some(apc_stats)))
.collect(),
PgoConfig::Instruction(pgo_program_idx_count) => {
create_apcs_with_instruction_pgo::<A>(blocks, pgo_program_idx_count, config, vm_config)
.into_iter()

View File

@@ -41,10 +41,6 @@ enum Commands {
#[clap(long)]
max_columns: Option<usize>,
/// When `--pgo-mode cell`, the optional max number of instructions per block
#[arg(long)]
max_block_instructions: Option<usize>,
#[arg(long)]
input: Option<u32>,
@@ -69,10 +65,6 @@ enum Commands {
#[clap(long)]
max_columns: Option<usize>,
/// When `--pgo-mode cell`, the optional max number of instructions per block
#[arg(long)]
max_block_instructions: Option<usize>,
#[arg(long)]
input: Option<u32>,
@@ -105,10 +97,6 @@ enum Commands {
#[clap(long)]
max_columns: Option<usize>,
/// When `--pgo-mode cell`, the optional max number of instructions per block
#[arg(long)]
max_block_instructions: Option<usize>,
#[arg(long)]
input: Option<u32>,
@@ -143,7 +131,6 @@ fn run_command(command: Commands) {
skip,
pgo,
max_columns,
max_block_instructions,
input,
apc_candidates_dir,
} => {
@@ -156,8 +143,7 @@ fn run_command(command: Commands) {
guest_opts.clone(),
stdin_from(input),
);
let pgo_config =
pgo_config(pgo, max_columns, max_block_instructions, execution_profile);
let pgo_config = pgo_config(pgo, max_columns, execution_profile);
let program = powdr_openvm::compile_guest(
&guest,
guest_opts,
@@ -175,7 +161,6 @@ fn run_command(command: Commands) {
skip,
pgo,
max_columns,
max_block_instructions,
input,
apc_candidates_dir,
} => {
@@ -188,8 +173,7 @@ fn run_command(command: Commands) {
guest_opts.clone(),
stdin_from(input),
);
let pgo_config =
pgo_config(pgo, max_columns, max_block_instructions, execution_profile);
let pgo_config = pgo_config(pgo, max_columns, execution_profile);
let program = powdr_openvm::compile_guest(
&guest,
guest_opts,
@@ -209,7 +193,6 @@ fn run_command(command: Commands) {
recursion,
pgo,
max_columns,
max_block_instructions,
input,
metrics,
apc_candidates_dir,
@@ -223,8 +206,7 @@ fn run_command(command: Commands) {
guest_opts.clone(),
stdin_from(input),
);
let pgo_config =
pgo_config(pgo, max_columns, max_block_instructions, execution_profile);
let pgo_config = pgo_config(pgo, max_columns, execution_profile);
let program = powdr_openvm::compile_guest(
&guest,
guest_opts,

View File

@@ -173,7 +173,7 @@ pub fn customize(
};
let max_total_apc_columns: Option<usize> = match pgo_config {
PgoConfig::Cell(_, max_total_columns, _) => max_total_columns.map(|max_total_columns| {
PgoConfig::Cell(_, max_total_columns) => max_total_columns.map(|max_total_columns| {
let total_non_apc_columns = original_config
.chip_inventory_air_metrics()
.values()

View File

@@ -301,8 +301,7 @@ fn instruction_index_to_pc(program: &Program<BabyBear>, idx: usize) -> u64 {
fn tally_opcode_frequency(pgo_config: &PgoConfig, exe: &VmExe<BabyBear>) {
let pgo_program_pc_count = match pgo_config {
PgoConfig::Cell(pgo_program_pc_count, _, _)
| PgoConfig::Instruction(pgo_program_pc_count) => {
PgoConfig::Cell(pgo_program_pc_count, _) | PgoConfig::Instruction(pgo_program_pc_count) => {
// If execution count of each pc is available, we tally the opcode execution frequency
tracing::debug!("Opcode execution frequency:");
pgo_program_pc_count
@@ -956,7 +955,7 @@ mod tests {
config.clone(),
PrecompileImplementation::SingleRowChip,
stdin,
PgoConfig::Cell(pgo_data, None, None),
PgoConfig::Cell(pgo_data, None),
None,
);
}
@@ -1048,7 +1047,7 @@ mod tests {
config.clone(),
PrecompileImplementation::SingleRowChip,
stdin.clone(),
PgoConfig::Cell(pgo_data.clone(), None, None),
PgoConfig::Cell(pgo_data.clone(), None),
None,
);
let elapsed = start.elapsed();
@@ -1134,7 +1133,7 @@ mod tests {
config.clone(),
PrecompileImplementation::SingleRowChip,
stdin,
PgoConfig::Cell(pgo_data, None, None),
PgoConfig::Cell(pgo_data, None),
None,
);
}
@@ -1213,7 +1212,7 @@ mod tests {
config.clone(),
PrecompileImplementation::SingleRowChip,
stdin.clone(),
PgoConfig::Cell(pgo_data.clone(), None, None),
PgoConfig::Cell(pgo_data.clone(), None),
None,
);
let elapsed = start.elapsed();
@@ -1287,7 +1286,7 @@ mod tests {
let apc_candidates_dir_path = apc_candidates_dir.path();
let config = default_powdr_openvm_config(guest.apc, guest.skip)
.with_apc_candidates_dir(apc_candidates_dir_path);
let is_cell_pgo = matches!(guest.pgo_config, PgoConfig::Cell(_, _, _));
let is_cell_pgo = matches!(guest.pgo_config, PgoConfig::Cell(_, _));
let compiled_program = compile_guest(
guest.name,
GuestOptions::default(),
@@ -1400,7 +1399,7 @@ mod tests {
test_machine_compilation(
GuestTestConfig {
pgo_config: PgoConfig::Cell(pgo_data, None, None),
pgo_config: PgoConfig::Cell(pgo_data, None),
name: GUEST,
apc: GUEST_APC,
skip: GUEST_SKIP_PGO,
@@ -1476,7 +1475,7 @@ mod tests {
test_machine_compilation(
GuestTestConfig {
pgo_config: PgoConfig::Cell(pgo_data, None, None),
pgo_config: PgoConfig::Cell(pgo_data, None),
name: GUEST_SHA256,
apc: GUEST_SHA256_APC_PGO,
skip: GUEST_SHA256_SKIP,
@@ -1611,7 +1610,7 @@ mod tests {
test_machine_compilation(
GuestTestConfig {
pgo_config: PgoConfig::Cell(pgo_data, None, None),
pgo_config: PgoConfig::Cell(pgo_data, None),
name: GUEST_KECCAK,
apc: GUEST_KECCAK_APC,
skip: GUEST_KECCAK_SKIP,
@@ -1662,7 +1661,7 @@ mod tests {
test_machine_compilation(
GuestTestConfig {
pgo_config: PgoConfig::Cell(pgo_data, Some(MAX_TOTAL_COLUMNS), None),
pgo_config: PgoConfig::Cell(pgo_data, Some(MAX_TOTAL_COLUMNS)),
name: GUEST_KECCAK,
apc: GUEST_KECCAK_APC_PGO_LARGE,
skip: GUEST_KECCAK_SKIP,