Date: 2025-11-20 Issue: PatternHelpers.v OOM during compilation despite conservative resource limits Status: Root cause identified, solutions proposed
Symptom: PatternHelpers.v killed by OOM (Out of Memory) during compilation
-j1), 18 coresComparison to Monolithic Build:
Paradox: Modular file (17.5% size) uses >3× memory of monolithic file (100% size).
PatternHelpers.v Characteristics:
$ wc -l PatternHelpers.v
591 PatternHelpers.v
$ grep -c "lia\." PatternHelpers.v
42
$ grep -c "induction" PatternHelpers.v
14
$ grep -c "destruct" PatternHelpers.v
89
Key Lemmas:
pattern_matches_at_has_mismatch (96 lines)
lia callspattern_has_leftmost_mismatch (136 lines)
lia callsTactic Overhead:
lia call generates large proof terms (SMT solver traces)destruct creates case splits (exponential in nesting depth)induction duplicates entire proof context for each caseDependency Graph:
PatternHelpers.v
├─ imports: Preservation.v
│ ├─ imports: Rules.v
│ │ ├─ imports: Types.v
│ │ └─ imports: Lib.v
│ └─ 182 lines (9.8KB)
│
└─ imports: InvariantProperties.v
└─ imports: AlgoState.v
Memory Impact:
Estimated Memory:
Types.vo: ~5GB
Lib.vo: ~5GB
Rules.vo: ~10GB
Preservation.vo: ~20GB (includes nested induction + lia)
InvariantProperties.vo: ~15GB
AlgoState.vo: ~10GB
Total dependencies: ~65GB
PatternHelpers.v proof terms: ~80-100GB (nested induction × lia)
Grand total: ~150-165GB
Why Monolithic is Smaller:
lia Tactic Mechanism:
Example:
Goal: i < j < k → i < k
Tactic: lia.
Proof term generated (simplified):
le_trans i j k
(le_S_n i (j-1) ...)
(le_S_n j (k-1) ...)
...
(* 50-100 lines of proof term per lia call *)
Nested Induction:
induction phones as [| ph phones' IH].
- (* Base case: nil *)
lia. (* Proof term 1 *)
- (* Inductive case *)
destruct pos.
+ (* pos = 0 *)
lia. (* Proof term 2, includes IH context *)
+ (* pos = S pos' *)
destruct (Phone_eqb ...).
* lia. (* Proof term 3, includes all above *)
* induction pattern.
-- lia. (* Proof term 4, nested induction context *)
-- ...
Memory Growth:
lia at depth n: Proof term size ~ C^nPatternHelpers.v:
lia calls at depth 3: ~8-j4, 150GB)systemd-run --user --scope \
-p MemoryMax=150G \
-p CPUQuota=2700% \
-p IOWeight=50 \
-p TasksMax=300 \
make -j4 2>&1 | tee /tmp/compile_j4_limited.log
Result: OOM killed at PatternHelpers.v
Reason: 4 files compiling × 40GB each = 160GB > 150GB
-j1, 126GB)systemd-run --user --scope \
-p MemoryMax=126G \
-p CPUQuota=1800% \
-p IOWeight=30 \
-p TasksMax=200 \
make -j1 2>&1 | tee /tmp/compile_serial_safe.log
Result: OOM killed at PatternHelpers.v (after 30+ min)
Reason: PatternHelpers.v alone > 126GB
# Original position_skipping_proof.v (3,379 lines)
make theories/position_skipping_proof.vo
Result: ✅ Success (~5 min, ~40GB peak)
Reason: No .vo loading, sequential proof checking
Strategy: Break into smaller modules to reduce peak memory.
Proposed Structure:
PatternHelpers_Basic.v (200 lines)
- pattern_length_bounds
- pattern_matches_at_spec
- Basic helper lemmas (no heavy proofs)
PatternHelpers_Mismatch.v (150 lines)
- pattern_matches_at_has_mismatch (96 lines)
- Depends on: PatternHelpers_Basic
PatternHelpers_Leftmost.v (241 lines)
- pattern_has_leftmost_mismatch (136 lines)
- Other advanced lemmas
- Depends on: PatternHelpers_Mismatch
Expected Memory:
Peak memory: 80GB < 126GB ✅
Trade-off:
Strategy: Temporarily admit memory-intensive lemmas, compile rest of system.
Implementation:
(* PatternHelpers.v *)
Lemma pattern_has_leftmost_mismatch :
forall pat s p,
pattern_matches_at pat s p = false ->
(length pat > 0)%nat ->
exists i,
(p <= i < p + length pat)%nat /\
...
Proof.
(* Original 136-line proof causes OOM *)
Admitted. (* Temporarily *)
(* Use in PatternOverlap.v *)
Lemma overlap_preserves_correctness : ...
Proof.
...
apply pattern_has_leftmost_mismatch. (* Uses admitted lemma *)
...
Qed.
Benefits:
Drawbacks:
When to Use:
Strategy: Reduce lia calls and proof term sizes.
Technique 1: Manual Arithmetic:
(* Before: Heavy lia *)
Lemma foo : forall n, n < n + 1.
Proof.
intros. lia. (* Generates 50-line proof term *)
Qed.
(* After: Direct proof *)
Lemma foo : forall n, n < n + 1.
Proof.
intros. apply Nat.lt_succ_diag_r. (* 1-line proof term *)
Qed.
Technique 2: Lemma Extraction:
(* Extract common lia patterns *)
Lemma pos_lt_sum : forall p len, p < p + len -> len > 0.
Proof. intros. lia. Qed. (* Prove once, heavy *)
(* Reuse in main proof *)
Lemma main : ...
Proof.
...
apply pos_lt_sum. (* Light reference *)
...
Qed.
Technique 3: Simplification Before lia:
(* Before *)
Lemma foo : forall a b c d e, complex_expr a b c d e > 0.
Proof.
intros. lia. (* Must solve huge constraint system *)
Qed.
(* After *)
Lemma foo : forall a b c d e, complex_expr a b c d e > 0.
Proof.
intros.
unfold complex_expr.
simpl.
(* Now much simpler *)
lia. (* Smaller problem *)
Qed.
Expected Savings: 30-50% reduction in proof term size
Effort: High (requires manual proof rewriting)
Strategy: Allocate more memory to Coq.
Current: 126GB (50% of 252GB)
Options:
# 75% of RAM
systemd-run --user --scope \
-p MemoryMax=189G \
-p CPUQuota=1800% \
-p IOWeight=30 \
-p TasksMax=200 \
make -j1
# 90% of RAM (risky, may freeze system)
systemd-run --user --scope \
-p MemoryMax=227G \
-p CPUQuota=1800% \
-p IOWeight=30 \
-p TasksMax=200 \
make -j1
Risks:
Recommendation: Try 189GB (75%) as fallback if Solution 1 fails
Strategy: Abandon modular decomposition, use original single file.
Benefits:
Drawbacks:
When to Use: Last resort if all else fails
Primary Plan: Solution 1 (Split PatternHelpers.v)
Rationale:
Fallback Plan: Solution 4 (189GB) if Solution 1 still OOMs
Steps:
Contents:
pattern_length_boundspattern_matches_at_specpattern_prefix_matchEstimated Memory: ~10GB
Contents:
Require Import PatternHelpers_Basic.
Lemma pattern_matches_at_has_mismatch :
forall pat s p,
pattern_matches_at pat s p = false ->
(length pat > 0)%nat ->
exists i,
(p <= i < p + length pat)%nat /\
(nth_error s i = None \/
exists ph pat_ph,
nth_error s i = Some ph /\
nth_error pat (i - p) = Some pat_ph /\
Phone_eqb ph pat_ph = false).
Proof.
(* 96-line proof from PatternHelpers.v *)
Qed.
Dependencies: PatternHelpers_Basic.vo (~10GB) Own Proofs: ~40GB Total Peak: ~50GB
Contents:
Require Import PatternHelpers_Mismatch.
Lemma pattern_has_leftmost_mismatch :
forall pat s p,
pattern_matches_at pat s p = false ->
(length pat > 0)%nat ->
exists i,
(p <= i < p + length pat)%nat /\
(nth_error s i = None \/
exists ph pat_ph,
nth_error s i = Some ph /\
nth_error pat (i - p) = Some pat_ph /\
Phone_eqb ph pat_ph = false) /\
(* i is the LEFTMOST mismatch *)
(forall j, (p <= j < i)%nat ->
exists s_ph pat_ph,
nth_error s j = Some s_ph /\
nth_error pat (j - p) = Some pat_ph /\
Phone_eqb s_ph pat_ph = true).
Proof.
(* 136-line proof from PatternHelpers.v *)
Qed.
(* Other advanced lemmas *)
Dependencies: PatternHelpers_Mismatch.vo (~50GB) Own Proofs: ~30GB Total Peak: ~80GB
-R theories Liblevenshtein.Phonetic.Verification
theories/Auxiliary/Types.v
theories/Auxiliary/Lib.v
theories/Core/Rules.v
theories/Patterns/Preservation.v
theories/Patterns/PatternHelpers_Basic.v
theories/Patterns/PatternHelpers_Mismatch.v
theories/Patterns/PatternHelpers_Leftmost.v
theories/Patterns/PatternOverlap.v
theories/Invariants/InvariantProperties.v
theories/Invariants/AlgoState.v
(* Old *)
Require Import Liblevenshtein.Phonetic.Verification.Patterns.PatternHelpers.
(* New *)
Require Import Liblevenshtein.Phonetic.Verification.Patterns.PatternHelpers_Basic.
Require Import Liblevenshtein.Phonetic.Verification.Patterns.PatternHelpers_Mismatch.
Require Import Liblevenshtein.Phonetic.Verification.Patterns.PatternHelpers_Leftmost.
With Split Solution:
Makefile dependency order:
1. Types.v, Lib.v (parallel) → 2 min
2. Rules.v → 3 min
3. Preservation.v → 8 min (has nested induction + lia)
4. InvariantProperties.v, AlgoState.v (parallel) → 5 min
5. PatternHelpers_Basic.v → 5 min
6. PatternHelpers_Mismatch.v → 15 min (heavy proof)
7. PatternHelpers_Leftmost.v → 20 min (heaviest proof)
8. PatternOverlap.v → 10 min
9. Position_Skipping_Proof.v → 15 min
Total: ~80 minutes (serial, -j1)
With Parallel (if memory allows -j2):
Total: ~50 minutes
# Terminal 1: Run compilation
cd /home/dylon/Workspace/f1r3fly.io/liblevenshtein-rust/docs/verification/phonetic
systemd-run --user --scope \
-p MemoryMax=126G \
-p CPUQuota=1800% \
-p IOWeight=30 \
-p TasksMax=200 \
make -j1 2>&1 | tee /tmp/coq_split_build.log
# Terminal 2: Monitor memory
watch -n 5 'ps aux | grep -E "rocq|coq" | grep -v grep | awk "{sum+=\$6} END {print sum/1024/1024 \" GB\"}"'
# Terminal 3: Check systemd cgroup
systemd-cgtop --user
Compilation Successful if:
Compilation Failed if:
The modular decomposition faces a fundamental memory challenge: PatternHelpers.v's complex nested proofs with heavy lia usage generate proof terms exceeding 126GB.
Root Cause: Proof complexity (O(n³) nested induction × 42 lia calls) + Module loading overhead (~65GB dependencies)
Recommended Solution: Split PatternHelpers.v into 3 smaller modules (Basic, Mismatch, Leftmost) to reduce peak memory to ~80GB.
Expected Outcome: ✅ Compilation succeeds within 126GB limit, preserves modularity and reusability.
Fallback: Increase memory limit to 189GB (75% RAM) or use Admitted temporarily.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |