From bc468427afde996ae1ca756564cd79d3a0bdbbf7 Mon Sep 17 00:00:00 2001 From: Sande33p Date: Mon, 19 Oct 2026 05:33:10 +0000 Subject: [PATCH 01/11] Add AstroPRISM: AION encoders, launcher fixes, and a tutorial AstroPRISM aligns a frozen AION-1 Base astronomy encoder with a frozen Qwen3-4B through two small projectors, so the model can describe calibrated galaxy images and DESI spectra. The checkpoint published at huggingface.co/Sand33p/AstroPRISM was trained with this code. That code had never been committed. Its model card says the training run "contained uncommitted AstroPRISM worktree changes", and it survived only as a git stash in one clone of the former BaseMM_PRISM repo, reachable by no branch. This commit brings it here, rebased onto current main rather than applied as-is: the stash forked at a commit 261 behind, and seven tracked files had moved under it. Provenance is checkable rather than asserted. The uploaded config/training_config.yaml matches src/conf/training/astroprism_joint_aion.yaml on task, batch_size 2, max_steps 200, the 576/273 token counts and the pinned AION revision 40541618104bab0fa85c8af68daeb867a720bb8c. Contents: - src/encoders/aion_{image,spectrum,multimodal}.py, the frozen AION encoders, plus Modality.SPECTRUM and its float32[7781] flux/ivar/mask/wavelength contract. - 11 src/data/astroprism_* modules and 9 Hydra configs for the three stages: image alignment, spectrum alignment, and the joint run. - docs/astroprism_tutorial.md, an Aurora walkthrough from environment to inference against the published weights, alongside the pilot plan, roster, prompt templates and benchmark survey under docs/applications/. - Three Aurora launcher fixes, independent of AstroPRISM but found with it: --batch executed the PBS script instead of submitting it, a stale tarball was never re-extracted, and the activate path ignored the tarball's nested .venv-deepspeed. tests/test_launch_aurora.py covers them and is wired into both launcher_contract and the make target, since its markers would otherwise leave it running in no CI job. Merge resolutions worth review. main's newer work wins where the two diverged: answer-only loss masking, aux decoder targets, generalized encoder unfreezing, the bioreason_grpo trainer dispatch, and the LoRA scopes. AstroPRISM's _tokens_per_instance_for_modality replaces main's inline encoder lookup because it also handles a shared encoder owner; it had to learn main's DNA parent-modality mapping to keep BioReason working, and it tolerates a missing ownership map so routing tests that build via __new__ still pass. main's trainer dispatch now forwards the validation and test loaders the joint path builds. The README support table gains a spectrum row and its config counts move from 26 to 29, which tests/test_docs_consistency.py enforces. Dropped from the stash: 29 unrelated files it also carried (atomistic encoders, VSP maze fixtures, DDP notes), which belong in their own PR. Verified on Aurora: 92 AstroPRISM tests, 92 multimodal contract tests, 23 launcher contracts, ruff clean, doc links resolve, and the site-path ratchet gains no entries. Co-Authored-By: Claude Opus 5 --- .github/workflows/pr_quality.yml | 1 + Makefile | 1 + README.md | 10 +- docs/applications/astroprism.md | 27 + .../astroprism_benchmark_landscape.md | 141 ++ docs/applications/astroprism_data.md | 117 ++ .../overlap_candidates_646.csv.gz | Bin 0 -> 39219 bytes .../astroprism_synthetic_data_prompts.md | 271 +++ docs/applications/astroprism_training_plan.md | 183 ++ docs/astroprism_tutorial.md | 246 +++ docs/index.md | 7 + experiments/astroprism_joint_aion.yaml | 34 + experiments/astroprism_stage1a.yaml | 42 + experiments/astroprism_stage1b_spectrum.yaml | 43 + src/conf/data/astroprism_joint_aion.yaml | 22 + src/conf/data/astroprism_stage1a.yaml | 18 + .../data/astroprism_stage1b_spectrum.yaml | 14 + .../prism_astroprism_joint_qwen3_4b.yaml | 52 + src/conf/model/prism_astroprism_qwen3_4b.yaml | 37 + .../prism_astroprism_spectrum_qwen3_4b.yaml | 36 + src/conf/training/astroprism_joint_aion.yaml | 31 + src/conf/training/astroprism_stage1a.yaml | 25 + .../training/astroprism_stage1b_spectrum.yaml | 25 + src/config.py | 108 +- src/data/astroprism_joint_batch_sampler.py | 225 +++ src/data/astroprism_joint_collate.py | 96 ++ src/data/astroprism_joint_dataset.py | 185 ++ src/data/astroprism_raw_images.py | 897 ++++++++++ src/data/astroprism_raw_spectra.py | 349 ++++ src/data/astroprism_spectrum_contract.py | 13 + src/data/astroprism_spectrum_facts.py | 651 +++++++ src/data/astroprism_stage1a.py | 807 +++++++++ src/data/astroprism_stage1a_collate.py | 128 ++ src/data/astroprism_stage1a_dataset.py | 588 +++++++ .../astroprism_stage1b_spectrum_collate.py | 145 ++ .../astroprism_stage1b_spectrum_dataset.py | 569 +++++++ src/data/multimodal.py | 9 +- src/encoders/__init__.py | 6 + src/encoders/aion_image.py | 276 +++ src/encoders/aion_multimodal.py | 415 +++++ src/encoders/aion_spectrum.py | 271 +++ src/modalities.py | 10 + src/model.py | 310 +++- src/modules/projector.py | 12 + src/train.py | 460 ++++- src/training/trainer_zone_a.py | 539 ++++-- tests/multimodal/test_make_dummy_batch.py | 9 + tests/multimodal/test_modality_enum.py | 7 +- tests/test_accept_astroprism_stage1a.py | 179 ++ tests/test_aion_image_encoder.py | 223 +++ tests/test_aion_model_wiring.py | 170 ++ tests/test_aion_multimodal_encoder.py | 404 +++++ tests/test_aion_spectrum_encoder.py | 257 +++ tests/test_aion_spectrum_model_wiring.py | 155 ++ tests/test_astroprism_joint_data.py | 314 ++++ .../test_astroprism_joint_train_entrypoint.py | 143 ++ tests/test_astroprism_raw_images.py | 443 +++++ tests/test_astroprism_raw_spectra.py | 282 ++++ tests/test_astroprism_spectrum_facts.py | 161 ++ tests/test_astroprism_stage1a.py | 268 +++ tests/test_astroprism_stage1a_dataset.py | 387 +++++ ...est_astroprism_stage1b_spectrum_dataset.py | 505 ++++++ ...prism_stage1b_spectrum_train_entrypoint.py | 176 ++ tests/test_astroprism_train_entrypoint.py | 140 ++ .../test_compose_astroprism_joint_adapters.py | 47 + tests/test_launch_aurora.py | 128 ++ tests/test_launch_aurora_web_vla.py | 12 + tests/test_prepare_astroprism_hf_dataset.py | 427 +++++ ...epare_astroprism_spectrum_caption_pilot.py | 234 +++ ...st_promote_astroprism_spectrum_captions.py | 335 ++++ tests/test_zone_a_aion_training_boundary.py | 154 ++ tools/accept_astroprism_stage1a.py | 496 ++++++ tools/compose_astroprism_joint_adapters.py | 174 ++ tools/extract_astroprism_raw_images.py | 115 ++ tools/extract_astroprism_raw_spectra.py | 109 ++ tools/generate_astroprism_joint_examples.py | 328 ++++ tools/launch_aurora.py | 31 +- tools/launch_aurora_web.py | 41 +- tools/prepare_astroprism_hf_dataset.py | 1491 +++++++++++++++++ ...epare_astroprism_spectrum_caption_pilot.py | 1158 +++++++++++++ tools/prepare_astroprism_stage1a.py | 76 + tools/promote_astroprism_spectrum_captions.py | 835 +++++++++ 82 files changed, 18668 insertions(+), 198 deletions(-) create mode 100644 docs/applications/astroprism.md create mode 100644 docs/applications/astroprism_benchmark_landscape.md create mode 100644 docs/applications/astroprism_data.md create mode 100644 docs/applications/astroprism_data/overlap_candidates_646.csv.gz create mode 100644 docs/applications/astroprism_synthetic_data_prompts.md create mode 100644 docs/applications/astroprism_training_plan.md create mode 100644 docs/astroprism_tutorial.md create mode 100644 experiments/astroprism_joint_aion.yaml create mode 100644 experiments/astroprism_stage1a.yaml create mode 100644 experiments/astroprism_stage1b_spectrum.yaml create mode 100644 src/conf/data/astroprism_joint_aion.yaml create mode 100644 src/conf/data/astroprism_stage1a.yaml create mode 100644 src/conf/data/astroprism_stage1b_spectrum.yaml create mode 100644 src/conf/model/prism_astroprism_joint_qwen3_4b.yaml create mode 100644 src/conf/model/prism_astroprism_qwen3_4b.yaml create mode 100644 src/conf/model/prism_astroprism_spectrum_qwen3_4b.yaml create mode 100644 src/conf/training/astroprism_joint_aion.yaml create mode 100644 src/conf/training/astroprism_stage1a.yaml create mode 100644 src/conf/training/astroprism_stage1b_spectrum.yaml create mode 100644 src/data/astroprism_joint_batch_sampler.py create mode 100644 src/data/astroprism_joint_collate.py create mode 100644 src/data/astroprism_joint_dataset.py create mode 100644 src/data/astroprism_raw_images.py create mode 100644 src/data/astroprism_raw_spectra.py create mode 100644 src/data/astroprism_spectrum_contract.py create mode 100644 src/data/astroprism_spectrum_facts.py create mode 100644 src/data/astroprism_stage1a.py create mode 100644 src/data/astroprism_stage1a_collate.py create mode 100644 src/data/astroprism_stage1a_dataset.py create mode 100644 src/data/astroprism_stage1b_spectrum_collate.py create mode 100644 src/data/astroprism_stage1b_spectrum_dataset.py create mode 100644 src/encoders/aion_image.py create mode 100644 src/encoders/aion_multimodal.py create mode 100644 src/encoders/aion_spectrum.py create mode 100644 tests/test_accept_astroprism_stage1a.py create mode 100644 tests/test_aion_image_encoder.py create mode 100644 tests/test_aion_model_wiring.py create mode 100644 tests/test_aion_multimodal_encoder.py create mode 100644 tests/test_aion_spectrum_encoder.py create mode 100644 tests/test_aion_spectrum_model_wiring.py create mode 100644 tests/test_astroprism_joint_data.py create mode 100644 tests/test_astroprism_joint_train_entrypoint.py create mode 100644 tests/test_astroprism_raw_images.py create mode 100644 tests/test_astroprism_raw_spectra.py create mode 100644 tests/test_astroprism_spectrum_facts.py create mode 100644 tests/test_astroprism_stage1a.py create mode 100644 tests/test_astroprism_stage1a_dataset.py create mode 100644 tests/test_astroprism_stage1b_spectrum_dataset.py create mode 100644 tests/test_astroprism_stage1b_spectrum_train_entrypoint.py create mode 100644 tests/test_astroprism_train_entrypoint.py create mode 100644 tests/test_compose_astroprism_joint_adapters.py create mode 100644 tests/test_launch_aurora.py create mode 100644 tests/test_prepare_astroprism_hf_dataset.py create mode 100644 tests/test_prepare_astroprism_spectrum_caption_pilot.py create mode 100644 tests/test_promote_astroprism_spectrum_captions.py create mode 100644 tests/test_zone_a_aion_training_boundary.py create mode 100755 tools/accept_astroprism_stage1a.py create mode 100644 tools/compose_astroprism_joint_adapters.py create mode 100644 tools/extract_astroprism_raw_images.py create mode 100644 tools/extract_astroprism_raw_spectra.py create mode 100644 tools/generate_astroprism_joint_examples.py create mode 100644 tools/prepare_astroprism_hf_dataset.py create mode 100644 tools/prepare_astroprism_spectrum_caption_pilot.py create mode 100644 tools/prepare_astroprism_stage1a.py create mode 100644 tools/promote_astroprism_spectrum_captions.py diff --git a/.github/workflows/pr_quality.yml b/.github/workflows/pr_quality.yml index a6d47c5..2342494 100644 --- a/.github/workflows/pr_quality.yml +++ b/.github/workflows/pr_quality.yml @@ -149,6 +149,7 @@ jobs: - name: Launcher contracts run: | pytest -q \ + tests/test_launch_aurora.py \ tests/test_launch_aurora_web_vla.py \ tests/platform/test_perlmutter_launch_contract.py \ tests/platform/test_hpc_bridge_contract.py \ diff --git a/Makefile b/Makefile index 1aad646..642267f 100644 --- a/Makefile +++ b/Makefile @@ -89,6 +89,7 @@ multimodal: ## Run the contract suite as CI's `multimodal_contract` job does # none of them -- this target is the only make path that does. launcher: ## Run the launcher contracts as CI's `launcher_contract` job does $(PYTHON) -m pytest -q \ + tests/test_launch_aurora.py \ tests/test_launch_aurora_web_vla.py \ tests/platform/test_perlmutter_launch_contract.py \ tests/platform/test_hpc_bridge_contract.py \ diff --git a/README.md b/README.md index 8d839a4..207f869 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,7 @@ Per-platform detail, including lockfile regeneration: | **Text** | [OLMo-1B / OLMo-3 7B](https://huggingface.co/allenai/OLMo-7B-0724-hf) | 2048 / 4096 | | **Image** | [SigLIP2](https://huggingface.co/google/siglip2-base-patch16-224) | 768 | | **Time series** | [Moirai 2.0](https://huggingface.co/Salesforce/moirai-2.0-R-small) / [Intern-S2 preview](https://huggingface.co/internlm/Intern-S2-Preview-397B) / [TimeOmni](https://arxiv.org/abs/2510.03255) | Variable by backend | +| **Spectrum** | [AION-1 Base](https://huggingface.co/polymathic-ai/aion-base) | 768 | | **DNA** | [Nucleotide Transformer v2 500M](https://huggingface.co/InstaDeepAI/nucleotide-transformer-v2-500m-multi-species) | 1024 | | **Geometry** | [Walrus](https://huggingface.co/polymathic-ai/walrus) | 512 | | **Graph** | GraphMAE2 (PyG) | 768 | @@ -193,10 +194,11 @@ actually exercised by shipped configs and CI, not what the architecture permits. | Modality | Status | Shipped model configs | Notes | |---|---|---|---| -| **Text** | Supported | 26 of 26 | Every config enables it. With an HF backbone, the backbone's own embeddings are used and `TextEncoder` is bypassed. | -| **Image** | Supported | 9 of 26 | Best-validated multimodal path; the basis of the scaling results below. | -| **Time series** | Supported | 14 of 26 | Five encoder backends (`linear`, `moirai`, `intern_s2`, `intern_s2_397b`, `timeomni`). One of two modalities with a vLLM inference processor (the other is image). | -| **DNA** | Supported | 2 of 26 | Nucleotide Transformer encoder (`src/encoders/dna.py`), used by the BioReason/KEGG path — see [docs/applications/bioreason_grpo.md](docs/applications/bioreason_grpo.md). Input-only. | +| **Text** | Supported | 29 of 29 | Every config enables it. With an HF backbone, the backbone's own embeddings are used and `TextEncoder` is bypassed. | +| **Image** | Supported | 11 of 29 | Best-validated multimodal path; the basis of the scaling results below. | +| **Time series** | Supported | 14 of 29 | Five encoder backends (`linear`, `moirai`, `intern_s2`, `intern_s2_397b`, `timeomni`). One of two modalities with a vLLM inference processor (the other is image). | +| **DNA** | Supported | 2 of 29 | Nucleotide Transformer encoder (`src/encoders/dna.py`), used by the BioReason/KEGG path — see [docs/applications/bioreason_grpo.md](docs/applications/bioreason_grpo.md). Input-only. | +| **Spectrum** | Supported | 2 of 29 | AION-1 Base codec + shared encoder (`src/encoders/aion_spectrum.py`), used by the AstroPRISM path — see [docs/astroprism_tutorial.md](docs/astroprism_tutorial.md). Input-only. | | **Graph** | Experimental | 0 | Requires `torch_geometric`. Runs in the per-modality sweep (172.7 samp/s) but no shipped model config enables it, and the handler is labelled untested in its own smoke config. | | **Table** | Experimental | 0 | TAPAS-based; runs in the sweep (65.5 samp/s). Input-only — no output decoder. No shipped model config enables it. | | **Geometry** | Experimental | 0 | Requires `walrus` and `the_well`. Runs in the sweep (69.4 samp/s) **on synthetic data** — no real PDEBench corpus is staged, and `GeometryEncoder` hardcodes `input_dim=6`. A `WALRUS_FALLBACK=1` stub exists for smoke runs and produces meaningless features. | diff --git a/docs/applications/astroprism.md b/docs/applications/astroprism.md new file mode 100644 index 0000000..029a40a --- /dev/null +++ b/docs/applications/astroprism.md @@ -0,0 +1,27 @@ +# AstroPrism: the 646-candidate pilot + +Start with the **646 positional matches between released paired AION galaxies and AION-Search image captions**. Recover and validate these objects on Aurora, train image-to-language caption alignment, prepare grounded spectral captions in parallel, then extend to image + spectrum + language, instruction SFT, and a later verifier-based RL experiment. + +The earlier goal of **10,000 training galaxies plus separate validation/test galaxies** remains a later expansion after this small pipeline works. + +## Contents + +| File | Purpose | +|---|---| +| [astroprism_training_plan.md](astroprism_training_plan.md) | AION-1/Qwen architecture, phases, splits, training settings and implementation work | +| [astroprism_synthetic_data_prompts.md](astroprism_synthetic_data_prompts.md) | Image verification, spectral facts/captions, joint captions, instruction conversations and validation prompts | +| [astroprism_benchmark_landscape.md](astroprism_benchmark_landscape.md) | AstroSpecQA, image benchmarks, paired/interleaved evaluation and RL evaluation boundaries | +| [astroprism_data.md](astroprism_data.md) | Dataset pins, roster checksum, field definitions and remote recovery procedure | +| [astroprism_data/overlap_candidates_646.csv.gz](astroprism_data/overlap_candidates_646.csv.gz) | Exact 646-row candidate roster with IDs, coordinates, separations and source shard/row locators | + +This page and the four it links to are the whole package. It is a **plan**: no AION inference, no astronomy-specific PRISM integration, no caption synthesis, no SFT and no RL is implemented here. The analysis scripts, audit tables and diagnostic arrays behind the roster are not part of it. + +## What is ready + +The positional-match roster is committed here. Its 646 paired catalog IDs all map uniquely into the official tutorial raw-file index, with matching DESI IDs for these candidates. Physical association, raw observation quality and compatibility of each caption with AION's exact image crop still need checking. Caption text has not yet been extracted for this roster. + +The proposal uses AION-1 Base's modality-specific codecs and shared frozen encoder, separate image/spectrum projectors, and Qwen3-4B. Nothing here establishes model readiness; the numbers below are proposals, not measurements. + +For a 646-object accepted pool, the proposed split is approximately **516 train / 65 validation / 65 test**, grouped by physical source before training or instruction generation. Rejections or grouping can change those counts. The 646 rows are the complete candidate pool, not 646 training objects plus additional holdouts. + +Remote data recovery, model loading, job submission and training are all subsequent work. [astroprism_data.md](astroprism_data.md) documents how to recover the captions and raw observations from the pinned public sources. diff --git a/docs/applications/astroprism_benchmark_landscape.md b/docs/applications/astroprism_benchmark_landscape.md new file mode 100644 index 0000000..0de9797 --- /dev/null +++ b/docs/applications/astroprism_benchmark_landscape.md @@ -0,0 +1,141 @@ +# Astronomy benchmark landscape for the PRISM galaxy pilot + +Updated: 2026-09-16. This is an evaluation plan, not an executed benchmark. Start from the [646 positional candidates](astroprism_data/overlap_candidates_646.csv.gz), confirm eligible physical pairs using the [data contract](astroprism_data.md), and reserve source-disjoint validation/test sets before any training. A provisional split is **516 train / 65 validation / 65 test**, conditional on all 646 being eligible and group constraints permitting it. The [training plan](astroprism_training_plan.md) covers image-caption alignment, spectrum/joint alignment, instruction SFT and later RL; [generation prompts](astroprism_synthetic_data_prompts.md) specify evidence permissions. The 10,000-training-galaxy design is a later expansion. + +## What to use + +Use three complementary evaluations: **scientific prediction from real observations**, **answers grounded in the available observation**, and **modality-dependence controls**. Existing resources cover parts of this combination. I did not locate a ready-to-run public benchmark that combines matched galaxy images, numerical 1D spectra, and independently validated open-ended joint answers on the same sources. This is a bounded search finding, not a claim that no such resource can exist. + +For this galaxy-only pilot, prioritize held-out redshift and spectral-feature tasks, human-supported morphology labels, and a small expert-reviewed joint QA set. Use paper-figure QA and text knowledge benchmarks as separate optional diagnostics. Do not report all-galaxy object classification or stellar subtype/temperature tasks as meaningful core pilot metrics. + +To stay close to the requested AION examples, its image evaluation already crossmatches Galaxy10 with Legacy DR10, yielding approximately 8,000 galaxies with four-band cutouts. This is a useful morphology protocol to adapt, but it does not establish how many of our paired pilot galaxies carry those labels. [AION §7.2.2](https://arxiv.org/html/2510.17960) + +## AstroSpecQA: the exact published reference + +AstroSpecQA uses numerical DESI spectra, held apart from DESI-SpecInstruct. Its **11,698 questions** comprise: + +| Task | Questions | Published metric | +|---|---:|---| +| Object class | 5,000 | Accuracy | +| Redshift, galaxies/QSOs | 3,247 | MAE, R² | +| Stellar subtype | 1,753 | Accuracy | +| Stellar effective temperature | 1,698 | MAE, R² | + +Appendix A.4 gives object counts: STAR 1,753; GALAXY 1,648; QSO 1,599. Stellar subtypes: K 620; G 386; M 322; F 274; WD 94; A 53; B 3; CV 1. Thus subtype accuracy can hide rare-class failure. Questions must not be counted as distinct physical objects. These details come from [§3.4, §5.1 and Appendix A.4](https://arxiv.org/html/2609.07102). + +The paper scores explanations with a fact-conditioned judge and a 100-output human check. The original benchmark files have not been obtained in this project, so its task design is a reference rather than an executable reproduction. [Explanation evaluation](https://arxiv.org/html/2609.07102) + +## Existing resources and their actual input modalities + +| Resource | What the model receives / does | Verified scope and availability | Role for PRISM | +|---|---|---|---| +| **Galaxy Zoo / Galaxy10** | Real galaxy images → morphology labels/vote fractions | Public catalogues/images. Galaxy Zoo DECaLS includes volunteer classifications and machine predictions; Galaxy Zoo DESI's 8.67M catalogue is automated morphology inference. [Official data](https://data.galaxyzoo.org/), [MMU Galaxy10](https://huggingface.co/datasets/UniverseTBD/mmu_gz10) | Strong image-grounding evaluation when the gold labels are human-supported. A machine prediction is a weaker reference, not independent human truth. | +| **AION galaxy-property evaluation** | Calibrated photometry, images and numerical spectra → physical-property estimates | Published PROVABGS evaluation, R², with photometry / photometry+image / photometry+image+spectrum conditions. [Paper §7.2.1](https://arxiv.org/html/2510.17960), [code](https://github.com/PolymathicAI/AION) | Closest existing multimodal scientific-prediction protocol. Keep input conditions explicit; the paper's strongest joint condition includes photometry. It is not an open-ended QA benchmark. | +| **AstroCLIP** | Real Legacy images and numerical DESI spectra → embeddings, cross-modal retrieval and downstream predictions | Public model/data workflow. [Official repository](https://github.com/PolymathicAI/AstroCLIP) | Encoder/probe and retrieval baselines. Retrieval performance alone does not establish grounded conversation. | +| **AION-Search** | Galaxy image / text query → retrieval ranking | Evaluates spirals, mergers and lenses using nDCG@10; also reports Galaxy10 classification. [Paper §§II.4–II.6](https://arxiv.org/html/2512.11982v2) | Optional retrieval diagnostic. Its synthetic captions are training supervision, not independent evaluation answers. | +| **AstroMMBench** | A rasterized astronomy-paper figure plus a four-option question | 621 MCQs across six subfields; generated from arXiv figure/context pairs and reviewed by experts. Accuracy overall/by subfield. [Paper](https://arxiv.org/html/2510.00063), [public release](https://huggingface.co/datasets/sjh11/AstroMMBench) | Optional figure-understanding/knowledge diagnostic. A paper figure, including a plotted spectrum, is not a numerical 1D DESI input or necessarily a raw sky cutout. | +| **AstroVLBench / ASIB** | Separate optical-image and DESI-spectrum tasks; spectrum plots and numerical CSVs are distributed | Public anonymous release; ASIB provides line-presence, broad-line and BPT tasks. [Dataset and task definitions](https://huggingface.co/datasets/anonymous4ai/AstroVLBench) | Possible later external check. Inspect the original inference path, spectrum frame and labels first; a native 1D adaptation is a new protocol. Its separate task populations do not establish matched image+spectrum joint QA. | + +Galaxy Zoo morphology is the closest ready-made image-only scientific check for this pilot. Match human labels to the exact student view; machine-generated catalogue predictions are weaker references. AstroMMBench is useful for figure QA, but calibrated four-band AION image inputs are not automatically interchangeable with arbitrary RGB paper figures. Neither resource supplies the native paired-encoder evaluation we need. + +## Proposed evaluation for the 646-candidate pilot + +Everything below is a **new PRISM evaluation proposal**, not an existing published benchmark or a reported result. + +### Source and split control + +Use the proposed 65 validation galaxies for model selection, inference-prompt/scorer development and threshold selection. Training-data teacher prompts and their demonstrations use training sources only. Lock the proposed 65 test galaxies before new caption generation or instruction synthesis. These are targets, not an existing split: resolve candidate matches first and record the actual numbers after quality cuts and grouping. Group all repeat observations of each physical object; prevent near-duplicate/overlapping image leakage, and report sky/field and observing-condition slices. Exclude test identities from every training stage, the RL reward pool, all augmentation descendants, and demonstrations supplied in prompts. + +Document possible AION pretraining exposure separately. A downstream split does not prove that the pretrained encoder never saw a source or an associated catalog label. Record object identity, survey/reduction, raw-data references, processed student views, input metadata, question, target provenance, evidence locations, scoring version, and allowed modalities in a frozen test manifest. + +### Core task panel + +| Panel | Proposed population | Inputs / questions | Scoring | +|---|---|---|---| +| **Galaxy redshift** | Up to 65 eligible test galaxies; report quality exclusions explicitly | Same numerical prediction task under image-only, spectrum-only and joint inputs; no target redshift supplied | MAE and R² for comparability; additionally bias, NMAD and outlier rate for normalized residual Δz/(1+z). Fix the outlier threshold on validation; a possible pilot choice is 0.05, explicitly a design choice. Report parse failures and numeric-answer coverage. | +| **Image morphology** | Human-labeled crossmatch subset `N_morph ≤ 65`, or a newly expert-labeled subset | Visible structure, orientation and feature presence. Use the exact crop/bands seen by the student | Macro-F1/balanced accuracy for declared discrete classes; proper probability scores for retained vote distributions. Keep machine-only labels in a separate analysis. Do not invent a guaranteed crossmatch count. | +| **Spectral evidence** | Valid, covered line/continuum items across the test sources; denominator varies by feature | Presence/strength of measured features, line ratios when valid, quality/coverage limitations | Precision/recall and macro-F1 on feature states; numeric errors with units/tolerances; separately score upper limits, missing coverage and masked regions. Compare against versioned measurements, not teacher prose. | +| **Joint answers** | All eligible test galaxies, up to 65, with expert review | One expert-reviewed joint question per source, evaluated with joint, image-only and spectrum-only views | Claim correctness, evidence attribution, unsupported-claim rate, and correct partial answers/abstention. Each view needs an admissible reference answer. Two independent reviewers adjudicate disagreements; automated judging is supplementary. | +| **Observation interventions** | A declared subset of the same test sources | Remove relevant lines or image regions, remove a modality, or swap one input with controlled alternatives | Direction/consistency of scientifically justified answer changes. Some swaps are observationally indistinguishable; do not force a mismatch verdict. | + +With 65 eligible test galaxies, the redshift panel yields up to **195 predictions on 65 galaxies**; evaluating one joint question under three input conditions yields up to **195 answers on those same galaxies**. These are evaluation records, not additional independent objects. With this small test set, report uncertainty and treat results as feasibility evidence rather than a general astronomy benchmark. Count spectrum-feature items and morphology labels separately. Bootstrap confidence intervals by galaxy, not by duplicated question, and report class, brightness, signal-to-noise and redshift slices. + +For the proposed redshift scorer, define `r = (z_pred − z_ref)/(1 + z_ref)`, bias as `median(r)`, and `NMAD = 1.4826 × median(|r − median(r)|)`. Report the fixed-threshold fraction `|r| > 0.05` separately from parse failures; report answer coverage on the complete eligible roster. These explicit scorer conventions are our proposal, not a claim that AstroSpecQA used them. + +For explanation scoring, retain concise claim-level evidence references in private audit records. The scoring facts should come from independently checked measurements/labels and expert review, rather than reuse of the same teacher's generated answer. A fluent explanation can be wrong even when a final label happens to be correct. + +### Controls and baselines + +- Compare direct instruction tuning against caption alignment followed by instruction tuning, with matched training data and evaluation sources. +- Evaluate image-only, spectrum-only, joint and text-only conditions. Include question-only or shuffled-input controls to expose answer priors. +- Train simple frozen-AION probes on the same training roster for numerical/categorical tasks. Match available inputs: an AION photometry+image+spectrum result is not an input-matched baseline for image+spectrum-only PRISM. +- Keep observational captions separate from supervised property estimates. Catalog masses, SFRs or redshifts may be valid prediction labels; their presence in a reference table does not make them directly visible evidence in every modality. +- In independent redshift evaluation, retain observed-frame spectra and exclude true-redshift rest-frame preprocessing and target-derived student features. Ensure a later conversation turn does not receive the target through an earlier answer. +- Use only information retained after AION preprocessing. A teacher's wider image, additional band, or uncropped wavelength range cannot justify a claim about evidence the student does not receive. +- Score final answers and evidence separately. Fix parsing rules, numeric conventions and invalid-answer handling before the test run. Never hide missing/malformed predictions by reporting accuracy only on successful parses. + +## Interleaving: input formatting versus scientific capability + +**Interleaving is a way to arrange the inputs, not a scientific task by itself.** A model may receive `text → image → text → spectrum → question` yet answer using only one modality. Conversely, a model receiving an image prefix followed by a spectrum prefix can perform a genuinely joint task. Demonstrate joint use through the scientific panels and observation interventions above, independently of serialization format. + +The initial proposal uses **all declared image/spectrum prefixes before the text conversation** for one galaxy. This is the baseline to implement and verify first. Incremental interleaved observations, repeated modality spans, and multiple-object conversations are optional future work; this note does not establish that the current PRISM loader, attention masks, or generation path support them. + +### Three distinct tests + +| Test | Example / contract | What it establishes | +|---|---|---| +| **Same object, different ordering** | Present the same galaxy image, spectrum, metadata and question before the answer, using image-first, spectrum-first, or text-separated layouts | Robustness to presentation order when the available evidence is unchanged. Hold the facts and permitted answer fixed; do not count permutations as new objects. | +| **New evidence over multiple turns** | Turn 1 provides an image and asks about visible morphology. Turn 2 introduces that galaxy's spectrum and asks for spectral interpretation or a combined assessment | Whether the model incorporates newly available evidence without using it prematurely. The first answer must have an image-only reference, the later answer a reference valid for the expanded input. | +| **Multiple objects and source binding** | Local alias A has image A and spectrum A; alias B has image B and spectrum B; the question explicitly asks about A, B or their comparison | Whether findings attach to the correct source. Reorder whole source-bound spans and include controlled distractors. Real survey IDs remain in the audit manifest; neutral A/B aliases may appear in the conversation. | + +These examples describe **proposed test structures**, not published example records or executed model results. Keep text references unambiguous: every “this galaxy,” “its spectrum,” or “the second source” must resolve to a source and an observation already available at that answer position. + +### Causal availability is part of the gold answer + +For each assistant turn, record the set of available observation IDs. Its question and reference answer must use that set, not modalities supplied later in the conversation. An image-only turn cannot claim line detections from a spectrum introduced afterward. Teacher generation and judging must follow the same turn-specific restriction. + +A prefix-only implementation that places **all** observations at the beginning would leak future spectra into earlier answers in an incremental-observation test. Do not run or claim this test until the model and collator implement the intended positions and causal visibility. For the initial prefix baseline, supply all observations up front and evaluate only that declared condition. + +Order permutations are scientifically comparable only when evidence availability and referents remain equivalent. Introducing a spectrum after an earlier answer changes the information available and therefore requires a different reference answer; it is not merely a harmless formatting permutation. + +### Engineering checks before scientific benchmarking + +Validate the serialization contract with small deterministic fixtures before model-quality comparisons: + +- Each modality placeholder maps to exactly one intended tensor/embedding span, with matching sample, source alias, modality type, ordering, token length and positions. +- Repeated images/spectra and multiple objects retain correct bindings through batching, collation, caching and generation; no span or cached feature crosses sample boundaries. +- Padding, truncation and missing modalities preserve valid span boundaries and attention masks; truncation must not silently retain a question whose required evidence was removed. +- Assistant-only loss labels exclude user text, modality prefixes, padding and audit sidecars. For incremental inputs, verify that earlier predictions cannot attend to later modality spans, including during cached generation. +- Dataset pixel-validity/bad-pixel masks, modality-presence masks, and language-model attention masks have separate named contracts and must not be conflated. + +These are **placeholder/alignment and causal-visibility checks**, not evidence of astronomy competence. After they pass, evaluate scientific correctness, source attribution, partial-answer behavior, order sensitivity and observation interventions on the held-out sources. Report both engineering pass/fail results and scientific scores; success in either category alone does not prove the other. + +### Evaluation after each training stage + +| Saved checkpoint | Required check | What a positive result establishes | +|---|---|---| +| Image caption alignment | Image claims against independently checked morphology/quality facts; compare image-conditioned and text-only output | Whether the image projector supplies useful visual evidence on unseen sources | +| Spectrum and joint caption alignment | Spectral feature correctness, uncertainty, image retention, and per-claim modality attribution | Whether adding the spectrum branch supports its own facts without erasing image grounding | +| Instruction SFT | All applicable task panels, answerability and multi-turn consistency; matched direct-SFT baseline if feasible | Whether the model follows instructions and keeps supported scientific content | +| Later RL pilot | The identical locked panels plus reward edge-case audit and comparison with the saved SFT checkpoint | Whether optimization of the independent verifier improves held-out scientific results, rather than only training reward | + +Save all intermediate checkpoints and define the comparisons before seeing test results. Use validation for early stopping and prompt changes. Score predeclared checkpoints on the test set in one frozen evaluation; repeated test inspection followed by edits turns it into development data. If exploratory tests are unavoidable, disclose them and obtain a fresh independent test pool for final claims. + +### RL reward and evaluation are separate + +The proposed RL pool contains training sources only and tasks with independent numerical/categorical checkers. Fix units, tolerances, quality eligibility and parse rules on training/validation data. Do not reward teacher agreement, answer length, eloquence or a model's self-assessment. A reward for a correct scalar does not verify the accompanying scientific explanation. Test the checker using valid, invalid, boundary and malformed outputs before policy optimization. + +Measure held-out correctness, unsupported claims, appropriate abstention and retained image/spectrum capabilities separately from reward. Keep a non-RL SFT checkpoint as the baseline. A gain on a reward built from the same synthetic teacher that produced training answers is not independent evidence; use catalog/measurement targets and expert-reviewed claims as applicable. Given the small pool, proceed beyond a bounded RL pilot only if the verifier is sound and the independent results justify it. + +### Optional external checks + +1. **Galaxy10 / human Galaxy Zoo labels:** morphology test after identity-overlap checks and matching the image view. A re-split or re-crop is a new evaluation condition, not an exact reproduction of a published score. +2. **AstroVLBench ASIB:** inspect and reproduce its original input protocol first, then separately identify any numerical-spectrum adaptation. Verify survey release, preprocessing, wavelength frame and target definitions; do not assume DESI releases are interchangeable. +3. **AstroMMBench:** only through an interface that handles its paper figures. Report figure QA separately from raw-observation performance. +4. **AION / AstroCLIP probes and retrieval:** use the same held-out source roster and declared inputs. Retrieval and property estimation do not by themselves establish instruction following. + +## Acceptance before implementing benchmark code + +Before scoring, assemble a reviewable evaluation manifest: exact source roster, overlap report, task eligibility counts, independent targets, questions/reference answers, per-view permissions, frozen scorers, and baseline configurations. Complete a small expert audit spanning ambiguous morphology, low signal-to-noise, nondetections, out-of-coverage features and aperture mismatch. Then freeze the benchmark and run the proposed comparisons. + +Document the resulting native image+spectrum QA panel as a **new AstroPrism pilot evaluation**, with its small sample size, construction and limitations. It should not be called AstroSpecQA reproduction unless the original benchmark data and evaluation protocol are obtained and preserved. diff --git a/docs/applications/astroprism_data.md b/docs/applications/astroprism_data.md new file mode 100644 index 0000000..fc99813 --- /dev/null +++ b/docs/applications/astroprism_data.md @@ -0,0 +1,117 @@ +# The 646 AION image–caption overlap candidates + +`overlap_candidates_646.csv.gz` is the complete, byte-identical candidate roster from the metadata audit completed on 2026-09-16. It contains identifiers, sky positions, angular separations, and exact source shard/row locators. **It does not contain caption text, image or spectrum arrays, features, or encoder weights.** Recover these on Aurora before accepting examples for training. + +- Compressed size: **39,219 bytes**; CSV data rows: **646**. +- SHA-256: `650935faa58953c9929e7379429bfb332a7486dab54544fedef64ca385abb3bc`. +- All 646 paired IDs and all 646 caption IDs are distinct. Every row has exactly one candidate in each direction within **1 arcsecond**; all three candidate/uniqueness flags are `True`. +- Median separation: **0.0191149 arcsec**; maximum: **0.2309893 arcsec**. All distances were independently checked with a spherical haversine calculation. +- The audit covered all **95,895** paired-feature objects and **164,911** Legacy DR10 caption records; it excluded the **110,702** HSC caption records. This is a positional candidate roster, not 646 already accepted training pairs. + +## License and attribution + +The roster is a derived table — identifiers, coordinates, separations and shard/row +locators — computed from the three pinned sources below. It carries no caption text, pixels, +spectra, features or weights. + +| Source | License | +|---|---| +| [`EiffL/desi_x_ls_aion`](https://huggingface.co/datasets/EiffL/desi_x_ls_aion) | CC BY 4.0 | +| [`astronolan/galaxy-descriptions`](https://huggingface.co/datasets/astronolan/galaxy-descriptions) | MIT | +| [AION tutorial HDF5](https://users.flatironinstitute.org/~polymathic/data/provabgs_desi_ls.hdf5) (Polymathic AI / Flatiron Institute) | see upstream | + +Both Hugging Face datasets permit redistribution of derived work with attribution, which this +section and the pinned revisions below provide. The underlying observations come from DESI and +the DESI Legacy Imaging Surveys; cite them directly in any downstream publication. + +## Pinned sources and identifier meanings + +| Source | Repository / location | Exact revision | +|---|---|---| +| Paired AION features | [`EiffL/desi_x_ls_aion`](https://huggingface.co/datasets/EiffL/desi_x_ls_aion/tree/57f7c7575260811ea076163bc743787d75b9aa49) | `57f7c7575260811ea076163bc743787d75b9aa49` | +| AION-Search image descriptions | [`astronolan/galaxy-descriptions`](https://huggingface.co/datasets/astronolan/galaxy-descriptions/tree/6890dc0c8fc793867c07a6ce4fce11e51c167d6e) | `6890dc0c8fc793867c07a6ce4fce11e51c167d6e` | +| Raw paired observations | [Official AION tutorial HDF5](https://users.flatironinstitute.org/~polymathic/data/provabgs_desi_ls.hdf5) | HTTP ETag `"6a62487d-1c2e3b0ad4"`; **121,034,705,620 bytes** | + +For captions use **config `default`, split `default`**, and require `survey == "legacy_dr10"`. Do not substitute the `tiny` split. The recorded caption locators refer directly to the pinned Parquet files, not an index in a filtered Hugging Face dataset. + +| Roster columns | Meaning | +|---|---| +| `paired_targetid` | String representation of the feature table's `object_id_provabgs`; use its PROVABGS namespace when recovering raw rows. | +| `caption_object_id` | Legacy imaging identifier from the caption table's `object_id`; it is a different namespace from the paired ID. | +| `paired_ra`, `paired_dec`, `caption_ra`, `caption_dec` | Source coordinates in degrees. | +| `separation_arcsec` | Spherical angular separation in arcseconds. | +| `paired_candidate_count`, `caption_candidate_count` | Number of neighbors within the matching radius in each direction. | +| `one_to_one_row_candidate`, `unique_source_ids`, `provisional_unambiguous_candidate` | Metadata matching flags; they do not certify physical identity, image quality, or caption correctness. | +| `paired_shard`, `caption_shard` | Paths relative to the corresponding pinned dataset repository. | +| `paired_row`, `caption_row` | **Zero-based row offset in the original, entire Parquet shard**, before any filtering. Row numbering continues across its row groups. | + +Read identifiers as strings or exact integers, never floating-point numbers; these astronomical IDs can exceed JavaScript's exact integer range. The row numbering was verified against the audit's `enumerate` over the complete Parquet table. + +## Recover the original captions on Aurora + +For either source, construct its file URL as: + +```text +https://huggingface.co/datasets/{repository}/resolve/{revision}/{shard_path} +``` + +The 646 caption candidates occupy just these two files, totaling **501,366,207 bytes** if downloaded in full: + +| Caption shard | SHA-256 of full Parquet file, from the pinned repository's LFS metadata | +|---|---| +| `data/default-00008-of-00097.parquet` | `b23b3742eb905213299d320d808841a5d208f140482c351d85436465c94cfd46` | +| `data/default-00009-of-00097.parquet` | `fea00a8b73d16fda8883009a1c401f0f7af99a57e508f25cfec7e47a05a2b652` | + +Minimal extraction recipe (future remote work; no downloads were executed to prepare this package): + +```python +# Requires huggingface_hub and pyarrow in the remote environment. +import csv, gzip +from collections import defaultdict +from huggingface_hub import hf_hub_download +import pyarrow.parquet as pq + +with gzip.open("docs/applications/astroprism_data/overlap_candidates_646.csv.gz", "rt") as f: + roster = list(csv.DictReader(f)) # IDs remain strings. +groups = defaultdict(list) +for row in roster: + groups[row["caption_shard"]].append(row) +for shard, candidates in groups.items(): + path = hf_hub_download( + repo_id="astronolan/galaxy-descriptions", repo_type="dataset", + revision="6890dc0c8fc793867c07a6ce4fce11e51c167d6e", filename=shard, + ) + # Verify the downloaded file's SHA-256 against the table above. + table = pq.read_table(path, columns=[ + "object_id", "survey", "ra", "dec", "caption", "summary", + ]) + for row in candidates: + item = table.slice(int(row["caption_row"]), 1).to_pylist()[0] + assert str(item["object_id"]) == row["caption_object_id"] + assert item["survey"] == "legacy_dr10" + # Also check coordinates against the roster before accepting the row. + # Save full caption, summary, original locator, and provenance together. +``` + +Use the full `caption` as the candidate target. Keep `summary` for provenance and inspection; it is not a substitute for validating the full description. These descriptions are synthetic weak supervision. Preserve the original text separately from any crop-validated revision. Grouping by shard avoids downloading the same file per object. Selective Parquet column/range reads can reduce transferred bytes further. + +The paired-feature locators span **18 shards** totaling **4,705,846,446 bytes** if downloaded in full. They allow retrieval of the released `embedding_image` and `embedding_spectrum` and verification of `object_id_provabgs`, `ra_provabgs`, and `dec_provabgs`. They do **not** locate raw pixels or flux arrays; use the raw tutorial source below for encoder inputs. Downloading these feature shards is optional when recomputing features with the official encoders. + +## Recover matching raw images and spectra + +The official tutorial HDF5 contains **115,404** unique PROVABGS objects, including every ID in the paired-feature release. Its URL is not a Git revision: check the recorded ETag and length before reading, and stop to revalidate if either changes. Avoid downloading the entire 121 GB file solely to extract these candidates; use a seekable HTTP-range reader with HDF5, require HTTP 206 responses, and restrict reads to IDs and selected rows. + +1. Read `provabgs_desi_edr_sv3/object_id` as exact IDs and construct `str(ID) -> raw row index`. Require uniqueness. Map each roster `paired_targetid` through this lookup; do not use the Parquet row offset as an HDF5 row offset. +2. Cross-check `desi/object_id`, `desi/ra`, `desi/dec`, the PROVABGS coordinates, and `legacysurvey/object_id`, `legacysurvey/ra`, `legacysurvey/dec`. Retain all original IDs and the raw source index. +3. Extract each selected row's `legacysurvey/image_array`, `image_band`, `image_ivar`, `image_mask`, `image_scale`, and `object_mask` under the `legacysurvey/` group, plus `desi/spectrum_flux`, `desi/spectrum_ivar`, `desi/spectrum_lambda`, and `desi/spectrum_mask`. Preserve units and masks. The image shape is `[4,160,160]`; the spectrum length is `7781`. +4. Read per-object band labels and use the official AION preprocessing; do not infer channel order from the array shape. Retain calibrated arrays, not only RGB previews. Apply and record the AION image crop and spectral preprocessing before caption acceptance. + +The full raw file has **95 rows where DESI and PROVABGS IDs differ**, so blindly joining through `desi/object_id` is unsafe. A local cross-check of this particular roster found **646 unique raw PROVABGS matches, with DESI and PROVABGS IDs agreeing for all 646**. Recheck this on extraction; any discrepancy must be resolved from source coordinates/identities or excluded, never silently relabeled. Identifier agreement still does not establish image–spectrum physical correspondence. + +## Acceptance and training handoff + +Before any candidate becomes supervision, verify the physical galaxy match, imaging survey/release, spectroscopic aperture, and that every accepted caption claim is supported by the **exact student image crop**. Remove or revise claims about structures or companions outside the crop. Record raw-source checksums, preprocessing, rejected claims, quality flags, and accepted/rejected status per object. Retain the original roster unchanged and write a separate accepted manifest. + +Freeze object-level train/validation/test assignments before deriving image captions, spectral captions, conversations, preferences, or reward examples. Every view, question, and generated derivative of one galaxy stays in the same split. The accepted count can be smaller than 646; 646 is the total candidate pool, not the training count. The training plan defines the subsequent image alignment, spectral captioning, joint model, instruction SFT, evaluation, and optional RL sequence. + +This directory is self-contained provenance and source-location information. The local analysis scripts, reports, environments, caches, and 32-object diagnostic arrays are not required to interpret this roster and are intentionally outside the portable package. diff --git a/docs/applications/astroprism_data/overlap_candidates_646.csv.gz b/docs/applications/astroprism_data/overlap_candidates_646.csv.gz new file mode 100644 index 0000000000000000000000000000000000000000..b89009966562c4df8f45aa525ddfd606081fe105 GIT binary patch literal 39219 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⪚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