halo2_base/gates/flex_gate/threads/
multi_phase.rs

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
use getset::CopyGetters;
use itertools::Itertools;

use crate::{
    gates::{circuit::CircuitBuilderStage, flex_gate::FlexGateConfigParams},
    utils::ScalarField,
    virtual_region::copy_constraints::SharedCopyConstraintManager,
    Context,
};

use super::SinglePhaseCoreManager;

/// Virtual region manager for [`FlexGateConfig`](super::super::FlexGateConfig) in multiple phases.
#[derive(Clone, Debug, Default, CopyGetters)]
pub struct MultiPhaseCoreManager<F: ScalarField> {
    /// Virtual region for each challenge phase. These cannot be shared across threads while keeping circuit deterministic.
    pub phase_manager: Vec<SinglePhaseCoreManager<F>>,
    /// Global shared copy manager
    pub copy_manager: SharedCopyConstraintManager<F>,
    /// Flag for witness generation. If true, the gate thread builder is used for witness generation only.
    #[getset(get_copy = "pub")]
    witness_gen_only: bool,
    /// The `unknown` flag is used during key generation. If true, during key generation witness `Value`s are replaced with `Value::unknown()` for safety.
    #[getset(get_copy = "pub")]
    use_unknown: bool,
}

impl<F: ScalarField> MultiPhaseCoreManager<F> {
    /// Creates a new [MultiPhaseCoreManager] with a default [SinglePhaseCoreManager] in phase 0.
    /// Creates an empty [SharedCopyConstraintManager] and sets `witness_gen_only` flag.
    /// * `witness_gen_only`: If true, the [MultiPhaseCoreManager] is used for witness generation only.
    ///     * If true, the gate thread builder only does witness asignments and does not store constraint information -- this should only be used for the real prover.
    ///     * If false, the gate thread builder is used for keygen and mock prover (it can also be used for real prover) and the builder stores circuit information (e.g. copy constraints, fixed columns, enabled selectors).
    ///         * These values are fixed for the circuit at key generation time, and they do not need to be re-computed by the prover in the actual proving phase.
    pub fn new(witness_gen_only: bool) -> Self {
        let copy_manager = SharedCopyConstraintManager::default();
        let phase_manager =
            vec![SinglePhaseCoreManager::new(witness_gen_only, copy_manager.clone())];
        Self { phase_manager, witness_gen_only, use_unknown: false, copy_manager }
    }

    /// Creates a new [MultiPhaseCoreManager] depending on the stage of circuit building. If the stage is [CircuitBuilderStage::Prover], the [MultiPhaseCoreManager] is used for witness generation only.
    pub fn from_stage(stage: CircuitBuilderStage) -> Self {
        Self::new(stage.witness_gen_only()).unknown(stage == CircuitBuilderStage::Keygen)
    }

    /// Mutates `self` to use the given copy manager in all phases and all threads.
    pub fn set_copy_manager(&mut self, copy_manager: SharedCopyConstraintManager<F>) {
        for pm in &mut self.phase_manager {
            pm.set_copy_manager(copy_manager.clone());
        }
        self.copy_manager = copy_manager;
    }

    /// Returns `self` with a given copy manager
    pub fn use_copy_manager(mut self, copy_manager: SharedCopyConstraintManager<F>) -> Self {
        self.set_copy_manager(copy_manager);
        self
    }

    /// Creates a new [MultiPhaseCoreManager] with `use_unknown` flag set.
    /// * `use_unknown`: If true, during key generation witness values are replaced with `Value::unknown()` for safety.
    pub fn unknown(mut self, use_unknown: bool) -> Self {
        self.use_unknown = use_unknown;
        for pm in &mut self.phase_manager {
            pm.use_unknown = use_unknown;
        }
        self
    }

    /// Clears all threads in all phases and copy manager.
    pub fn clear(&mut self) {
        for pm in &mut self.phase_manager {
            pm.clear();
        }
        self.copy_manager.lock().unwrap().clear();
    }

    /// Returns a mutable reference to the [Context] of a gate thread. Spawns a new thread for the given phase, if none exists.
    /// * `phase`: The challenge phase (as an index) of the gate thread.
    pub fn main(&mut self, phase: usize) -> &mut Context<F> {
        self.touch(phase);
        self.phase_manager[phase].main()
    }

    /// Spawns a new thread for a new given `phase`. Returns a mutable reference to the [Context] of the new thread.
    /// * `phase`: The phase (index) of the gate thread.
    pub fn new_thread(&mut self, phase: usize) -> &mut Context<F> {
        self.touch(phase);
        self.phase_manager[phase].new_thread()
    }

    /// Returns a mutable reference to the [SinglePhaseCoreManager] of a given `phase`.
    pub fn in_phase(&mut self, phase: usize) -> &mut SinglePhaseCoreManager<F> {
        self.phase_manager.get_mut(phase).unwrap()
    }

    /// Populate `self` up to Phase `phase` (inclusive)
    pub(crate) fn touch(&mut self, phase: usize) {
        while self.phase_manager.len() <= phase {
            let _phase = self.phase_manager.len();
            let pm = SinglePhaseCoreManager::new(self.witness_gen_only, self.copy_manager.clone())
                .in_phase(_phase);
            self.phase_manager.push(pm);
        }
    }

    /// Returns some statistics about the virtual region.
    pub fn statistics(&self) -> GateStatistics {
        let total_advice_per_phase =
            self.phase_manager.iter().map(|pm| pm.total_advice()).collect::<Vec<_>>();

        let total_fixed: usize = self
            .copy_manager
            .lock()
            .unwrap()
            .constant_equalities
            .iter()
            .map(|(c, _)| *c)
            .sorted()
            .dedup()
            .count();

        GateStatistics { total_advice_per_phase, total_fixed }
    }

    /// Auto-calculates configuration parameters for the circuit
    ///
    /// * `k`: The number of in the circuit (i.e. numeber of rows = 2<sup>k</sup>)
    /// * `minimum_rows`: The minimum number of rows in the circuit that cannot be used for witness assignments and contain random `blinding factors` to ensure zk property, defaults to 0.
    pub fn calculate_params(&self, k: usize, minimum_rows: Option<usize>) -> FlexGateConfigParams {
        let max_rows = (1 << k) - minimum_rows.unwrap_or(0);
        let stats = self.statistics();
        // we do a rough estimate by taking ceil(advice_cells_per_phase / 2^k )
        // if this is too small, manual configuration will be needed
        let num_advice_per_phase = stats
            .total_advice_per_phase
            .iter()
            .map(|count| (count + max_rows - 1) / max_rows)
            .collect::<Vec<_>>();
        let num_fixed = (stats.total_fixed + (1 << k) - 1) >> k;

        let params = FlexGateConfigParams { num_advice_per_phase, num_fixed, k };
        #[cfg(feature = "display")]
        {
            for (phase, num_advice) in stats.total_advice_per_phase.iter().enumerate() {
                println!("Gate Chip | Phase {phase}: {num_advice} advice cells",);
            }
            println!("Total {} fixed cells", stats.total_fixed);
            log::info!("Auto-calculated config params:\n {params:#?}");
        }
        params
    }
}

/// Basic statistics
pub struct GateStatistics {
    /// Total advice cell count per phase
    pub total_advice_per_phase: Vec<usize>,
    /// Total distinct constants used
    pub total_fixed: usize,
}