Two-stage training stack for image → CadQuery code generation, built on Qwen3-VL-2B with a swappable backbone mixin (Qwen2-VL / Qwen3-VL today; Qwen2.5-VL adapter ready).
text2cad-bench.Online IoU + Failures eval runs every eval_steps against fixed subsets
of BenchCAD val / DeepCAD test / Fusion360 test. Greedy + max@8 (best of
8 candidates at t=1.0). Logged to W&B alongside eval_loss. Checkpoints
push to a public/private HF model repo by a non-blocking thread on every
save_steps.
Where the recipe lives: the active SFT config is
configs/sft/iid.yaml(full-data control). The §7 paper study uses 4 sibling configs (baseline.yaml,ood.yaml,ood_enhance.yaml,iid.yaml); see configs/sft/README.md. Its header comments (lines 1-38) document mix design + per-source step share. Latest run learnings (data cleaning numbers, trajectory vs prior versions, op_loss interpretation, rendering pitfalls) are indocs/learnings_2026-04-29.md.
Required tokens (in .env):
HF_TOKEN — Hula0401 read+write (for cad-sft data + ckpt push)BenchCAD_HF_TOKEN — only if pushing back to BenchCAD/* upstreamWANDB_API_KEYDISCORD_WEBHOOK_URL (optional) — for live eval-result posting# 1. Clone
git clone https://github.com/miachen0401/cadrille.git
cd cadrille
# 2. Credentials
cp .env.example .env && $EDITOR .env
# 3. Install everything (apt deps, uv venv, torch+cu124, pytorch3d, cadquery,
# flash-attn, Open3D source build w/ headless rendering, all training data)
bash scripts/setup.sh --data # ~30-50 min wall-clock; idempotent
# 4. Reload PATH
source ~/.local/bin/env 2>/dev/null || source ~/.bashrc
# 5. Pre-flight check (catches bad pkl / missing PNGs / stale filter caches
# BEFORE the GPU boots up — has saved several launches)
set -a && source .env && set +a
uv run python -m scripts.preflight_check \
--config configs/sft/iid.yaml
# 6. Train iid (full-data baseline; 50 k steps, ~25 h on A100)
nohup uv run python -u -m train.sft \
--config configs/sft/iid.yaml \
> logs/iid_$(date +%Y%m%d_%H%M%S).log 2>&1 &
# 7. (optional) Live Discord posting — fires on every eval landing
nohup bash scripts/watch_eval_post_discord.sh \
"$(ls -t logs/v3_*.log | head -1)" \
/ephemeral/checkpoints/sft-s50k-lr2e-4-b8a4-img-<timestamp> \
> logs/watch.log 2>&1 &
For RL: bash scripts/setup.sh --full then uv run python -m train.rl.train --config configs/rl/a100.yaml. RL is BLOCKED on SFT IoU ≥ 0.8 on
DeepCAD/Fusion360 (greedy) — current ckpt is at ~0.6 greedy / 0.68 max@8.
common/ # cross-cutting helpers
model.py # Cadrille mixin (FourierPointEncoder + custom forward)
# works on any *VLForConditionalGeneration backbone
datasets.py # CadRecode / Text2CAD / BenchCad / CadRecode20k loaders
meshio.py # render_img + MeshDataset
metrics.py # compute_metrics / compute_reward / cq_reward_workers
train/
sft/
train.py # entry → python -m train.sft --config <yaml>
online_eval.py # IoU + Failures + max@8 callback
hf_uploader.py # background ckpt push (non-blocking)
rl/
train.py algorithms/{cppo,dpo}.py eval.py mine.py …
eval/
bench_sweep.py # multi-dataset × multi-temp × max@N IoU sweep
passk.py # pass@k estimator
pipeline.py runner.py render.py report.py
data_prep/ # one-time data materializers (HF → local pkl + PNGs)
fetch_cad_sft.py # all 5 v3 SFT sources
fetch_benchcad.py # eval split
prerender_dataset.py # mesh → 4-view _render.png
render_benchcad_easy.py # cadquery → 4-view PNG, parallel + shard-aware
merge_benchcad_easy_renders.py + upload_shards_to_hula0401.py
scripts/
setup.sh # one-click installer (--data / --full)
preflight_check.py # MUST run before launching SFT
watch_eval_post_discord.sh # tail log → post IoU + collage to Discord
analysis/ # one-off diagnostics
op_distribution_plot.py # per-source op stats + plots
source_grid_render.py # 100-render grid per training source
eval_to_discord.py # full per-eval Discord post (history table,
# comparison table, max@8, run lineage, etc)
parse_cq.py # regex-based op extractor
bench/ # training-throughput benchmarks
experiments/ # off-main investigations (cadevolve, repair_lora, …)
configs/sft/ # YAML configs (one per recipe; v3 is current)
configs/rl/
tests/test_refactor_safety.py # 81 import + math tests, ~5 s
docs/ # session reports, diagnostics, learnings_<date>.md
All training data lives under Hula0401/cad-sft/<name>/ on HF and
mirrors to data/<name>/. Use data_prep/fetch_cad_sft.py to pull.
| source | items | mode | step % | what it adds | watch out |
|---|---|---|---|---|---|
benchcad | 11k | img | 2% | BC val anchor — the smallest source but highest BC val leverage (v2 saw +0.16 BC at 50× weight) | tiny op vocab, over-fits if cranked too high |
cad_iso_106 | 122k | img | 23% | only source with non-trivial fillet (19% of items) — drives rare-op recall on real parts | family-clustered → must shuffle before render |
benchcad_simple | 77k | img | 15% | base-plane/workplane diversity, fast clean codes | lowest op-count (median 4) — collapses if too heavy |
text2cad_bench (img) | 53k | img | 5.5% | extra image variants of t2c codes | overlaps with text variant — pick one mode per sample |
text2cad_bench (text) | 53k | text | 5.5% | only text-conditioned path | descriptions vary in quality |
cad_recode_bench | 472k | img | 49% | the workhorse — wide op vocab, primary long-tail signal | 1.65 epochs/item; rare ops still mode-collapse at eval |
Per-source op stats (n=500, seed=42): median ops/code is 4-5 across
all sources except cad_recode_20k (7, NOT in v3). See
docs/op_distribution_2026-04-29/ for plots.
v3 cleaning: 80% drop on trivial families + code-hash dedup removed 37% of benchcad / 24% of iso / 29% of t2c — pure compute saved.
benchcad-easy — 109k items, 55 shards, render_img 100% on
Hula0401/cad-sft/benchcad-easy/. Same simple_* family as benchcad,
10× larger. Likely v4 candidate (+0.03-0.05 BC val expected).cad_recode_20k — 19k legacy. Has the highest ops/case (median 7)
but small; superseded by the 472k cad_recode_bench variant.| bucket | source | size | n_per_eval |
|---|---|---|---|
| BenchCAD val | local 90/10 split of BenchCAD/cad_bench | 1,973 | 50 |
| DeepCAD test | Hula0401/deepCAD_test | 8,046 | 50 |
| Fusion360 test | Hula0401/fusion360_test_mesh | 1,725 | 50 |
| recode20k train (probe) | sampled from training corpus | – | 50 |
The RL gate is greedy IoU ≥ 0.8 on DeepCAD/Fusion360. Greedy + max@8
(8 candidates at t=1.0) report every eval_steps per bucket.
| config | mix / weights | total weight | bs × acc | max_steps | notes |
|---|---|---|---|---|---|
configs/sft/iid.yaml | bench 15 / iso 158 / simple 111 / easy 116 / t2c-img 55 / t2c-text 55 / recode-bench 490 | 1000 | 8 × 4 = 32 | 50,000 | Full-data control (no holdout). 60% HQ / 40% bench-stack. §7 IID upper bound. |
configs/sft/ood_enhance.yaml | same as iid + 10-family holdout filter | 1000 | same | 50,000 | Holdout + benchcad-easy supplement. §7 OOD-with-supplement line. |
configs/sft/ood.yaml | bench 21 / iso 222 / simple 157 / t2c-img 55 / t2c-text 55 / recode-bench 490 | 1000 | same | 50,000 | Holdout, no benchcad-easy. §7 OOD-plain line. |
configs/sft/baseline.yaml | t2c-img 53 / t2c-text 53 / recode-bench 472 | 578 | same | 50,000 | HQ-only floor (no benchcad data). §7 no-bench line. |
configs/sft/curriculum_qwen3vl_2b.yaml | 3-phase curriculum | varies | 8 × 4 | 20,000 | Plateaued early; phase-3 (8:1:1) hurt DC. |
configs/sft/qwen3vl_2b_recode_30k_clean.yaml | recode-only (single source) | – | 8 × 4 | 30,000 | Smoke / single-source baseline. |
configs/sft/smoke.yaml | minimal | – | 1 × 1 | 100 | CI-style end-to-end check. |
configs/rl/a100.yaml | DeepCAD/Fusion360 hard-mined | per paper | – | 12k+ | RL post-train (BLOCKED on SFT ≥ 0.8 greedy). |
All historical configs are kept for reference. See
docs/learnings_2026-04-29.mdfor the rationale of each version's mix change.
# Multi-temp, multi-sample sweep on a checkpoint
uv run python -m eval.bench_sweep \
--ckpt /ephemeral/checkpoints/<run>/checkpoint-final \
--base-model Qwen/Qwen3-VL-2B-Instruct \
--backbone qwen3_vl \
--datasets benchcad,deepcad,fusion360 \
--temps 0,0.4,0.75,1.0 \
--n-samples 8 --limit 50 --seed 42 \
--modality img --batch-size 8 --score-workers 16 \
--out eval_outputs/sweep_<tag>
# Pass@k
uv run python -m eval.passk \
--checkpoint <ckpt> \
--val-dir data/deepcad_test_mesh \
--n-samples 5 --k-values 1,5
# Per-source op-distribution diagnostic (no GPU)
uv run python -m scripts.analysis.op_distribution_plot
Online (during training) eval is automatic; results post to W&B and (if configured) Discord. Each eval emits per-bucket greedy IoU + exec rate + op_loss + rare_op_recall; every 2nd eval also runs max@8.
Frequently-used scripts, grouped by job. All are runnable as-is — no flags required for the common path. See each script's docstring for full options.
| Tool | What it does | Usage |
|---|---|---|
scripts/preflight_check.py | Validate every source in a YAML mix (pkl loadable, PNGs resolve, code lengths, filter-cache freshness). Run before every SFT launch. | uv run python -m scripts.preflight_check --config configs/sft/<run>.yaml |
scripts/launch_chain_runs.sh | Wait for one SFT PID to exit, then launch baseline → ood → iid back-to-back (v3-722 + v4-OOD retro evals run between v4 stop and baseline start). | WAIT_PID=<pid> nohup bash scripts/launch_chain_runs.sh > logs/launch_chain.log 2>&1 & |
scripts/stop_v4_at_24k.sh | Watchdog: wait for checkpoint-{N}/model.safetensors to write + stabilise, then SIGTERM the training PID. Tunable target step / PID / ckpt root via env vars. | V4_PID=<pid> TARGET_STEP=24000 nohup bash scripts/stop_v4_at_24k.sh > logs/stop.log 2>&1 & |
scripts/watch_eval_post_discord.sh | Poll predictions/step-N.jsonl files, fire eval_to_discord.py per new step, mark .posted to avoid duplicates. | nohup bash scripts/watch_eval_post_discord.sh <train.log> <ckpt-out-dir> > logs/watch.log 2>&1 & |
scripts/analysis/eval_report.py | Per-step v3 vs v4 unified table (greedy + max@8 + ops, IID/OOD aware ★ on OOD row). Posts to Discord with --post. | uv run python -m scripts.analysis.eval_report --step <N> --post |
scripts/analysis/eval_to_discord.py | Trajectory collage poster — renders mesh trajectories per bucket, IID/OOD tagged anchors. | called by the watcher above; or directly via --step N |
| Monitor index | Full catalogue + conventions (red [OOD] / blue [IID] tags, holdout sync rules). | scripts/analysis/README_MONITOR.md |
| Tool | What it does | Usage |
|---|---|---|
data_prep/prerender_dataset.py | Render all *.stl under a dir to {stem}_render.png (4-view 268×268). Idempotent, parallel-safe. Run once per dataset before training; common.meshio.render_img then loads PNGs instead of rendering on the fly. | uv run python -m data_prep.prerender_dataset --dir data/<name> [--workers 8] |
data_prep/render_benchcad_easy.py | cadquery .py → 4-view 268×268 PNG, parallel + shard-aware (resumes per shard). Use for raw HF parquets that ship code-only. | uv run python -m data_prep.render_benchcad_easy --shards 15-54 [--workers 8] |
data_prep/merge_benchcad_easy_renders.py | Merge per-shard rendered PNGs into one parquet ready for HF upload. | uv run python -m data_prep.merge_benchcad_easy_renders |
common/meshio.py::render_img | Library function: load {stem}_render.png if it exists, else render on the fly via Open3D headless. Used at training time. | from common.meshio import render_img; img = render_img(stl_path) |
| Tool | What it does | Usage |
|---|---|---|
scripts/eval_v3_cad_bench_722.py | v3 ckpt → full BenchCAD/cad_bench_722 (722 samples × 106 families). Saves per-case .py + metadata.jsonl + per-family summary.json. §7 IID upper-bound. | uv run python scripts/eval_v3_cad_bench_722.py --ckpt <v3-ckpt> --out eval_outputs/v3_cad_bench_722 |
scripts/eval_v4_ood_retro.py | Iterate saved checkpoints in a run dir, run stratified 50-OOD (10 fams × 5) on each. Backfills clean OOD trajectory for runs that pre-date the IID/OOD split refactor. | uv run python scripts/eval_v4_ood_retro.py --ckpts <ckpt-root> --out eval_outputs/v4_ood_retro |
eval/bench_sweep.py | Multi-dataset × multi-temp × max@N IoU sweep — see Eval section above. | uv run python -m eval.bench_sweep ... |
eval/passk.py | pass@k estimator. | uv run python -m eval.passk ... |
scripts/analysis/main_figure_v2.py | §7 4-panel paper figure: OOD rare_recall climb / OOD ess_pass plateau / final-step gap / IID control. | uv run python -m scripts.analysis.main_figure_v2 |
scripts/analysis/v4_failure_analysis.py | Per-OOD-family IoU + exec + ess bars — appendix figure. | uv run python -m scripts.analysis.v4_failure_analysis |
| Tool | What it does | Usage |
|---|---|---|
data_prep/fetch_cad_sft.py | Pull all 5 v3 SFT sources from Hula0401/cad-sft/. | uv run python -m data_prep.fetch_cad_sft |
data_prep/fetch_benchcad.py | Pull eval split (90/10 of BenchCAD/cad_bench). Generates STLs + writes _skipped.jsonl for tessellation failures. | uv run python -m data_prep.fetch_benchcad |
data_prep/build_holdout_split.py | Partition train.pkl → train_v4_holdout.pkl by held-out family list. Required before launching ood*.yaml configs. | uv run python -m data_prep.build_holdout_split --holdout-families <fam1> <fam2> ... |
data_prep/build_benchcad_easy.py | Build data/benchcad-easy/{train,*.py,*.png} from local rendered shards (default range 15–54). | uv run python -m data_prep.build_benchcad_easy [--shard-range 15-54] |
data_prep/repack_recode_to_bench.py | AST-rewrite cad-recode codes to BenchCAD shell style (one-time; outputs already on HF). | uv run python -m data_prep.repack_recode_to_bench |
uv run pytest tests/test_refactor_safety.py # 81 fast tests, ~5 s
tests/test_iou.py / test_pipeline.py / test_cppo_step.py cover
GPU+ckpt-bound paths and should run after substantial training-loop changes.
Built on cadrille (ICLR 2026) — base VL backbone + FourierPointEncoder + CPPO algorithm. Our additions:
online_eval.py IoU + max@8 callbackLicense: see upstream cadrille.
Two-stage training stack for image → CadQuery code generation, built on Qwen3-VL-2B with a swappable backbone mixin (Qwen2-VL / Qwen3-VL today; Qwen2.5-VL adapter ready).
text2cad-bench.Online IoU + Failures eval runs every eval_steps against fixed subsets
of BenchCAD val / DeepCAD test / Fusion360 test. Greedy + max@8 (best of
8 candidates at t=1.0). Logged to W&B alongside eval_loss. Checkpoints
push to a public/private HF model repo by a non-blocking thread on every
save_steps.
Where the recipe lives: the active SFT config is
configs/sft/iid.yaml(full-data control). The §7 paper study uses 4 sibling configs (baseline.yaml,ood.yaml,ood_enhance.yaml,iid.yaml); see configs/sft/README.md. Its header comments (lines 1-38) document mix design + per-source step share. Latest run learnings (data cleaning numbers, trajectory vs prior versions, op_loss interpretation, rendering pitfalls) are indocs/learnings_2026-04-29.md.
Required tokens (in .env):
HF_TOKEN — Hula0401 read+write (for cad-sft data + ckpt push)BenchCAD_HF_TOKEN — only if pushing back to BenchCAD/* upstreamWANDB_API_KEYDISCORD_WEBHOOK_URL (optional) — for live eval-result posting# 1. Clone
git clone https://github.com/miachen0401/cadrille.git
cd cadrille
# 2. Credentials
cp .env.example .env && $EDITOR .env
# 3. Install everything (apt deps, uv venv, torch+cu124, pytorch3d, cadquery,
# flash-attn, Open3D source build w/ headless rendering, all training data)
bash scripts/setup.sh --data # ~30-50 min wall-clock; idempotent
# 4. Reload PATH
source ~/.local/bin/env 2>/dev/null || source ~/.bashrc
# 5. Pre-flight check (catches bad pkl / missing PNGs / stale filter caches
# BEFORE the GPU boots up — has saved several launches)
set -a && source .env && set +a
uv run python -m scripts.preflight_check \
--config configs/sft/iid.yaml
# 6. Train iid (full-data baseline; 50 k steps, ~25 h on A100)
nohup uv run python -u -m train.sft \
--config configs/sft/iid.yaml \
> logs/iid_$(date +%Y%m%d_%H%M%S).log 2>&1 &
# 7. (optional) Live Discord posting — fires on every eval landing
nohup bash scripts/watch_eval_post_discord.sh \
"$(ls -t logs/v3_*.log | head -1)" \
/ephemeral/checkpoints/sft-s50k-lr2e-4-b8a4-img-<timestamp> \
> logs/watch.log 2>&1 &
For RL: bash scripts/setup.sh --full then uv run python -m train.rl.train --config configs/rl/a100.yaml. RL is BLOCKED on SFT IoU ≥ 0.8 on
DeepCAD/Fusion360 (greedy) — current ckpt is at ~0.6 greedy / 0.68 max@8.
common/ # cross-cutting helpers
model.py # Cadrille mixin (FourierPointEncoder + custom forward)
# works on any *VLForConditionalGeneration backbone
datasets.py # CadRecode / Text2CAD / BenchCad / CadRecode20k loaders
meshio.py # render_img + MeshDataset
metrics.py # compute_metrics / compute_reward / cq_reward_workers
train/
sft/
train.py # entry → python -m train.sft --config <yaml>
online_eval.py # IoU + Failures + max@8 callback
hf_uploader.py # background ckpt push (non-blocking)
rl/
train.py algorithms/{cppo,dpo}.py eval.py mine.py …
eval/
bench_sweep.py # multi-dataset × multi-temp × max@N IoU sweep
passk.py # pass@k estimator
pipeline.py runner.py render.py report.py
data_prep/ # one-time data materializers (HF → local pkl + PNGs)
fetch_cad_sft.py # all 5 v3 SFT sources
fetch_benchcad.py # eval split
prerender_dataset.py # mesh → 4-view _render.png
render_benchcad_easy.py # cadquery → 4-view PNG, parallel + shard-aware
merge_benchcad_easy_renders.py + upload_shards_to_hula0401.py
scripts/
setup.sh # one-click installer (--data / --full)
preflight_check.py # MUST run before launching SFT
watch_eval_post_discord.sh # tail log → post IoU + collage to Discord
analysis/ # one-off diagnostics
op_distribution_plot.py # per-source op stats + plots
source_grid_render.py # 100-render grid per training source
eval_to_discord.py # full per-eval Discord post (history table,
# comparison table, max@8, run lineage, etc)
parse_cq.py # regex-based op extractor
bench/ # training-throughput benchmarks
experiments/ # off-main investigations (cadevolve, repair_lora, …)
configs/sft/ # YAML configs (one per recipe; v3 is current)
configs/rl/
tests/test_refactor_safety.py # 81 import + math tests, ~5 s
docs/ # session reports, diagnostics, learnings_<date>.md
All training data lives under Hula0401/cad-sft/<name>/ on HF and
mirrors to data/<name>/. Use data_prep/fetch_cad_sft.py to pull.
| source | items | mode | step % | what it adds | watch out |
|---|---|---|---|---|---|
benchcad | 11k | img | 2% | BC val anchor — the smallest source but highest BC val leverage (v2 saw +0.16 BC at 50× weight) | tiny op vocab, over-fits if cranked too high |
cad_iso_106 | 122k | img | 23% | only source with non-trivial fillet (19% of items) — drives rare-op recall on real parts | family-clustered → must shuffle before render |
benchcad_simple | 77k | img | 15% | base-plane/workplane diversity, fast clean codes | lowest op-count (median 4) — collapses if too heavy |
text2cad_bench (img) | 53k | img | 5.5% | extra image variants of t2c codes | overlaps with text variant — pick one mode per sample |
text2cad_bench (text) | 53k | text | 5.5% | only text-conditioned path | descriptions vary in quality |
cad_recode_bench | 472k | img | 49% | the workhorse — wide op vocab, primary long-tail signal | 1.65 epochs/item; rare ops still mode-collapse at eval |
Per-source op stats (n=500, seed=42): median ops/code is 4-5 across
all sources except cad_recode_20k (7, NOT in v3). See
docs/op_distribution_2026-04-29/ for plots.
v3 cleaning: 80% drop on trivial families + code-hash dedup removed 37% of benchcad / 24% of iso / 29% of t2c — pure compute saved.
benchcad-easy — 109k items, 55 shards, render_img 100% on
Hula0401/cad-sft/benchcad-easy/. Same simple_* family as benchcad,
10× larger. Likely v4 candidate (+0.03-0.05 BC val expected).cad_recode_20k — 19k legacy. Has the highest ops/case (median 7)
but small; superseded by the 472k cad_recode_bench variant.| bucket | source | size | n_per_eval |
|---|---|---|---|
| BenchCAD val | local 90/10 split of BenchCAD/cad_bench | 1,973 | 50 |
| DeepCAD test | Hula0401/deepCAD_test | 8,046 | 50 |
| Fusion360 test | Hula0401/fusion360_test_mesh | 1,725 | 50 |
| recode20k train (probe) | sampled from training corpus | – | 50 |
The RL gate is greedy IoU ≥ 0.8 on DeepCAD/Fusion360. Greedy + max@8
(8 candidates at t=1.0) report every eval_steps per bucket.
| config | mix / weights | total weight | bs × acc | max_steps | notes |
|---|---|---|---|---|---|
configs/sft/iid.yaml | bench 15 / iso 158 / simple 111 / easy 116 / t2c-img 55 / t2c-text 55 / recode-bench 490 | 1000 | 8 × 4 = 32 | 50,000 | Full-data control (no holdout). 60% HQ / 40% bench-stack. §7 IID upper bound. |
configs/sft/ood_enhance.yaml | same as iid + 10-family holdout filter | 1000 | same | 50,000 | Holdout + benchcad-easy supplement. §7 OOD-with-supplement line. |
configs/sft/ood.yaml | bench 21 / iso 222 / simple 157 / t2c-img 55 / t2c-text 55 / recode-bench 490 | 1000 | same | 50,000 | Holdout, no benchcad-easy. §7 OOD-plain line. |
configs/sft/baseline.yaml | t2c-img 53 / t2c-text 53 / recode-bench 472 | 578 | same | 50,000 | HQ-only floor (no benchcad data). §7 no-bench line. |
configs/sft/curriculum_qwen3vl_2b.yaml | 3-phase curriculum | varies | 8 × 4 | 20,000 | Plateaued early; phase-3 (8:1:1) hurt DC. |
configs/sft/qwen3vl_2b_recode_30k_clean.yaml | recode-only (single source) | – | 8 × 4 | 30,000 | Smoke / single-source baseline. |
configs/sft/smoke.yaml | minimal | – | 1 × 1 | 100 | CI-style end-to-end check. |
configs/rl/a100.yaml | DeepCAD/Fusion360 hard-mined | per paper | – | 12k+ | RL post-train (BLOCKED on SFT ≥ 0.8 greedy). |
All historical configs are kept for reference. See
docs/learnings_2026-04-29.mdfor the rationale of each version's mix change.
# Multi-temp, multi-sample sweep on a checkpoint
uv run python -m eval.bench_sweep \
--ckpt /ephemeral/checkpoints/<run>/checkpoint-final \
--base-model Qwen/Qwen3-VL-2B-Instruct \
--backbone qwen3_vl \
--datasets benchcad,deepcad,fusion360 \
--temps 0,0.4,0.75,1.0 \
--n-samples 8 --limit 50 --seed 42 \
--modality img --batch-size 8 --score-workers 16 \
--out eval_outputs/sweep_<tag>
# Pass@k
uv run python -m eval.passk \
--checkpoint <ckpt> \
--val-dir data/deepcad_test_mesh \
--n-samples 5 --k-values 1,5
# Per-source op-distribution diagnostic (no GPU)
uv run python -m scripts.analysis.op_distribution_plot
Online (during training) eval is automatic; results post to W&B and (if configured) Discord. Each eval emits per-bucket greedy IoU + exec rate + op_loss + rare_op_recall; every 2nd eval also runs max@8.
Frequently-used scripts, grouped by job. All are runnable as-is — no flags required for the common path. See each script's docstring for full options.
| Tool | What it does | Usage |
|---|---|---|
scripts/preflight_check.py | Validate every source in a YAML mix (pkl loadable, PNGs resolve, code lengths, filter-cache freshness). Run before every SFT launch. | uv run python -m scripts.preflight_check --config configs/sft/<run>.yaml |
scripts/launch_chain_runs.sh | Wait for one SFT PID to exit, then launch baseline → ood → iid back-to-back (v3-722 + v4-OOD retro evals run between v4 stop and baseline start). | WAIT_PID=<pid> nohup bash scripts/launch_chain_runs.sh > logs/launch_chain.log 2>&1 & |
scripts/stop_v4_at_24k.sh | Watchdog: wait for checkpoint-{N}/model.safetensors to write + stabilise, then SIGTERM the training PID. Tunable target step / PID / ckpt root via env vars. | V4_PID=<pid> TARGET_STEP=24000 nohup bash scripts/stop_v4_at_24k.sh > logs/stop.log 2>&1 & |
scripts/watch_eval_post_discord.sh | Poll predictions/step-N.jsonl files, fire eval_to_discord.py per new step, mark .posted to avoid duplicates. | nohup bash scripts/watch_eval_post_discord.sh <train.log> <ckpt-out-dir> > logs/watch.log 2>&1 & |
scripts/analysis/eval_report.py | Per-step v3 vs v4 unified table (greedy + max@8 + ops, IID/OOD aware ★ on OOD row). Posts to Discord with --post. | uv run python -m scripts.analysis.eval_report --step <N> --post |
scripts/analysis/eval_to_discord.py | Trajectory collage poster — renders mesh trajectories per bucket, IID/OOD tagged anchors. | called by the watcher above; or directly via --step N |
| Monitor index | Full catalogue + conventions (red [OOD] / blue [IID] tags, holdout sync rules). | scripts/analysis/README_MONITOR.md |
| Tool | What it does | Usage |
|---|---|---|
data_prep/prerender_dataset.py | Render all *.stl under a dir to {stem}_render.png (4-view 268×268). Idempotent, parallel-safe. Run once per dataset before training; common.meshio.render_img then loads PNGs instead of rendering on the fly. | uv run python -m data_prep.prerender_dataset --dir data/<name> [--workers 8] |
data_prep/render_benchcad_easy.py | cadquery .py → 4-view 268×268 PNG, parallel + shard-aware (resumes per shard). Use for raw HF parquets that ship code-only. | uv run python -m data_prep.render_benchcad_easy --shards 15-54 [--workers 8] |
data_prep/merge_benchcad_easy_renders.py | Merge per-shard rendered PNGs into one parquet ready for HF upload. | uv run python -m data_prep.merge_benchcad_easy_renders |
common/meshio.py::render_img | Library function: load {stem}_render.png if it exists, else render on the fly via Open3D headless. Used at training time. | from common.meshio import render_img; img = render_img(stl_path) |
| Tool | What it does | Usage |
|---|---|---|
scripts/eval_v3_cad_bench_722.py | v3 ckpt → full BenchCAD/cad_bench_722 (722 samples × 106 families). Saves per-case .py + metadata.jsonl + per-family summary.json. §7 IID upper-bound. | uv run python scripts/eval_v3_cad_bench_722.py --ckpt <v3-ckpt> --out eval_outputs/v3_cad_bench_722 |
scripts/eval_v4_ood_retro.py | Iterate saved checkpoints in a run dir, run stratified 50-OOD (10 fams × 5) on each. Backfills clean OOD trajectory for runs that pre-date the IID/OOD split refactor. | uv run python scripts/eval_v4_ood_retro.py --ckpts <ckpt-root> --out eval_outputs/v4_ood_retro |
eval/bench_sweep.py | Multi-dataset × multi-temp × max@N IoU sweep — see Eval section above. | uv run python -m eval.bench_sweep ... |
eval/passk.py | pass@k estimator. | uv run python -m eval.passk ... |
scripts/analysis/main_figure_v2.py | §7 4-panel paper figure: OOD rare_recall climb / OOD ess_pass plateau / final-step gap / IID control. | uv run python -m scripts.analysis.main_figure_v2 |
scripts/analysis/v4_failure_analysis.py | Per-OOD-family IoU + exec + ess bars — appendix figure. | uv run python -m scripts.analysis.v4_failure_analysis |
| Tool | What it does | Usage |
|---|---|---|
data_prep/fetch_cad_sft.py | Pull all 5 v3 SFT sources from Hula0401/cad-sft/. | uv run python -m data_prep.fetch_cad_sft |
data_prep/fetch_benchcad.py | Pull eval split (90/10 of BenchCAD/cad_bench). Generates STLs + writes _skipped.jsonl for tessellation failures. | uv run python -m data_prep.fetch_benchcad |
data_prep/build_holdout_split.py | Partition train.pkl → train_v4_holdout.pkl by held-out family list. Required before launching ood*.yaml configs. | uv run python -m data_prep.build_holdout_split --holdout-families <fam1> <fam2> ... |
data_prep/build_benchcad_easy.py | Build data/benchcad-easy/{train,*.py,*.png} from local rendered shards (default range 15–54). | uv run python -m data_prep.build_benchcad_easy [--shard-range 15-54] |
data_prep/repack_recode_to_bench.py | AST-rewrite cad-recode codes to BenchCAD shell style (one-time; outputs already on HF). | uv run python -m data_prep.repack_recode_to_bench |
uv run pytest tests/test_refactor_safety.py # 81 fast tests, ~5 s
tests/test_iou.py / test_pipeline.py / test_cppo_step.py cover
GPU+ckpt-bound paths and should run after substantial training-loop changes.
Built on cadrille (ICLR 2026) — base VL backbone + FourierPointEncoder + CPPO algorithm. Our additions:
online_eval.py IoU + max@8 callbackLicense: see upstream cadrille.