LevelSet-ILT Architecture Audit¶
Status: continuous-mask gradient-descent ILT, faithful to the level-set / SimpleILT-style formulation surveyed in Yang2023_LithoBench §3.3.
Last audited: 2026-05-23 against the formulation in Yang2023_LithoBench (NeurIPS 2023, openreview.net/forum?id=jWHU4b7Yk6) and the lineage papers cited in the model docstring. Re-audited 2026-05-23 (later) to reflect the threading.Lock cache-guard backport from OpenILTModel.
OpenLithoHub ships a LevelSetILTModel adapter (src/openlithohub/models/levelset_ilt.py) used as the canonical ILT-via-gradient-descent baseline. This page records what we implement, what the surveyed formulation specifies, and where they diverge.
Audit method¶
Compared models/levelset_ilt.py against:
Yang2023_LithoBench§3.3 — survey of the SimpleILT-style L2 + total-variation formulation. The model docstring cites this as the open-access substitute for the paywalled Granik / Pang lineage.- Pang, Liu & Abrams, Inverse lithography technology principles in practice (Proc. SPIE 5992, 2005) — original level-set / continuous-mask formulation.
- Poonawala & Milanfar, Mask design for optical microlithography — an inverse imaging problem (IEEE TIP 16(3), 2007) — gradient-based inversion of the imaging operator.
Confidence:
- A — verified against the source in this repo.
- B — verified against the LithoBench survey description; constants here (
lr=0.1,sigma_px=2.0,tv_weight=0.01) are this project's defaults, not literal paper values — the model docstring already states this explicitly. - C — derived from a paper note rather than an audited primary source.
What the surveyed formulation specifies¶
| Item | Survey / paper specification | Confidence |
|---|---|---|
| Loss | ‖Z − Z_T‖² + λ · TV(mask) — L2 fidelity + total-variation regularisation. |
B (LithoBench §3.3) |
| Mask representation | Continuous, sigmoid-bounded [0, 1]. Update via gradient descent on a logit. |
B |
| Optimizer | Gradient descent — surveyed as "SGD/Adam"; the survey does not pin a choice. | B |
| Forward model | Convolution against an optical PSF (Gaussian) or a Hopkins/SOCS partial-coherent kernel. | B |
| Resist | Sigmoid-thresholded aerial intensity. | B |
| Process window | Multi-corner sweeps (dose × defocus) feed into a weighted-mean fidelity loss. | C (Pang2005 / Granik) |
What OpenLithoHub implements¶
src/openlithohub/models/levelset_ilt.py :: LevelSetILTModel:
| Item | OpenLithoHub | Matches survey? | Confidence |
|---|---|---|---|
| Loss (default) | mse_loss(resist_nom, target) + tv_weight · TV(mask) per predict() ll. ~270–272. tv_weight=0.01 default. |
Yes in shape. Constants are our defaults. | A |
| Loss (PW mode) | process_window=True swaps the nominal-only fidelity term for workflow.process_window.pw_fidelity_loss over DEFAULT_PW_CORNERS. Currently only on the gaussian forward model — Hopkins multi-corner sweeps deferred (an explicit ValueError enforces this). |
Yes in shape — multi-corner mean. Hopkins coverage is a known gap. | A |
| Mask init | mask_logit = 4 · target − 2 per ll. ~213. After sigmoid, ≈ 0.12 at target=0 and ≈ 0.88 at target=1 — non-saturated. |
Different from OpenILTModel's 2 · target − 1 PixelInit but the survey does not pin an init. Our choice gives a slightly stronger initial signal. |
A |
| Optimizer | torch.optim.Adam(lr=0.1). |
Different from OpenILTModel's SGD-with-momentum — Adam is the project's choice for this baseline. Survey-compatible. |
A |
| Forward model | gaussian (default) — simulate_aerial_image(sigma_px=2.0). hopkins — simulate_aerial_image_hopkins(...) reusing _utils/hopkins.py. SOCS kernels are cached per (grid_size, device). |
Yes — both modes match the survey's optical-core options. | A |
| Resist | differentiable_threshold(aerial, threshold=0.5, steepness=50.0). |
Yes. Constants are our defaults. | A |
| Hopkins kernel cache | Cached per (grid_size, device), invalidated when hopkins_params changes. Lock-guarded via threading.Lock — concurrent predict() callers cannot race on the check-then-rebuild. Pattern backported from OpenILTModel 2026-05-23. |
Project-level — survey is silent. | A |
| Compile path | compile_forward=True triggers torch.compile(hopkins_fn, mode="reduce-overhead") cached per (shape, device, dtype, forward_model). Falls back to eager on torch < 2.0 / no-Triton platforms. |
Project-level optimisation beyond the survey scope. | A |
| Checkpoint / resume | checkpoint_dir + save_freq + resume_from: writes mask_logit + Adam state every save_freq iterations; resume from any such file restarts the iteration counter and runs the remainder. |
Project-level — survey is silent. | A |
| Halo / receptive field | RECEPTIVE_FIELD_PX = 64, matching OpenILTModel. |
Project-level — survey does not tile. | A |
Findings¶
-
Implementation tracks the surveyed formulation faithfully. L2 + TV with sigmoid-bounded mask logit, gradient descent, optical-core convolution, sigmoid-thresholded resist — all present.
-
Two divergences from
OpenILTModelare deliberate and worth knowing: (a) Init is4·target−2here vs.2·target−1inOpenILTModel. Stronger initial signal. (b) Optimizer is Adam (lr=0.1) here vs. SGD+momentum inOpenILTModel. Adam converges faster on the L2+TV loss surface; SGD-with-momentum is the SimpleILT-paper-faithful choice for the L2+PVBand objective. Both are survey-consistent — the survey does not pin the optimizer. -
Process-window mode does not cover the Hopkins forward.
predict(process_window=True, forward_model="hopkins")raisesValueError. This is a documented gap, not a bug — multi-corner Hopkins sweeps would multiply SOCS kernel rebuilds (defocus changes invalidate kernels perHopkinsSimulator._hparams_match). Closing it requires either per-corner kernel caches (mirroring whatcompute_pvband(simulator=…)now does) or a PW-corner sweep that holds defocus constant. -
Hopkins kernel cache is lock-guarded (as of 2026-05-23). Concurrent
predict()callers serialize onself._cache_lock(athreading.Lock) for the check-then-rebuild path, mirroring the pattern inOpenILTModel. The compile-cache (self._compiled_hopkins_cache) is still unguarded — concurrent callers with different(shape, device, dtype, forward_model)keys are fine, but two callers with the same key racing on the first compile will both runtorch.compile. That's a minor perf hit, not a correctness bug, so it is left for now. Multithread safety for the rest ofpredict()(e.g., the optimizer step) is still not guaranteed — keep one model instance per request unless you've audited the full path. -
save_freq=Nsemantics double-checked.it+1is the count of completed steps; resume restarts atit+1and runsiterations - (it+1)more steps — total work equals an uninterrupted run. The inline comment at ll. ~283–286 is correct.
Implications for users¶
- Default vs. SimpleILT-paper-faithful choice: if you want the SimpleILT formulation byte-for-byte, use
OpenILTModel.LevelSetILTModelis the "level-set / continuous-mask gradient descent" baseline, more general than SimpleILT. - Process-window mode + Hopkins: unsupported as of 2026-05-23. Use either
process_window=True, forward_model="gaussian"orprocess_window=False, forward_model="hopkins". - Multithread safety: the Hopkins kernel cache is now lock-guarded, but the rest of
predict()(optimizer state, in-place tensor updates) has not been audited for concurrent use. Use a per-process model instance behind any concurrent inference path. - Citation hygiene: when reporting numbers, cite
Yang2023_LithoBenchfor the survey lineage. The Pang2005 / Poonawala2007 papers are the deeper lineage references — cite them only if you also vouch for the level-set numerical equivalence (this implementation is closer to "continuous-mask gradient descent" than to Pang's Hamilton-Jacobi level-set evolution).
Re-audit triggers¶
Re-run this audit when any of the following change:
- Mask init scheme (
mask_logit = ...) changes. - Optimizer or learning-rate default changes.
process_windowgains Hopkins forward support.- The
_compiled_hopkins_cachegains lock guards, or the rest ofpredict()is audited for full multithread safety. - The Pang2005 / Poonawala2007 paper PDFs land in
docs/papers/— items marked C can be promoted to A.