EmergentTwilight/MMCoT / docs /thinkmorph.md
EmergentTwilight's picture
|
download
raw
51.7 kB

ThinkMorph workflows

Canonical BAGEL-family framework

OURS-Bagel/ owns new training work for the shared BAGEL architecture, including both models/Bagel-7B-MoT and models/ThinkMorph-7B. SFT v1 is implemented with strict YAML, a typed configurable interleaved protocol, protocol-fingerprinted Parquet conversion, native full-parameter FSDP, global-count-normalized CE/flow-MSE, EMA, atomic resume, and per-update TensorBoard/JSONL. LoRA opd implements the text/flow contract below; rl is reserved.

The first canonical configs are OURS-Bagel/config/sft/thinkmorph_paper_bagel7b.yaml and the one-update thinkmorph7b_sft_smoke.yaml. New jobs use cluster_jobs.submit --source-profile=bagel; outputs live below experiments/output/<base-model>/<dataset>/<version>/...; see docs/artifacts.md.

BAGEL SFT supports two explicit controls. updates preserves ThinkMorph's 32K token-packed, optimizer-step recipe. epochs uses fixed source-sample packs and derives the update target from dataset size and logical batch. The first rank-128 LoRA candidate uses the full original trainable component scope, constant LR 2e-5, four samples per rank on four GPUs, logical batch 16, and ten sample-counted epochs. Its full parity audit is docs/research/20260914-225604_bagel_thinkmorph_lora_sft_parity_audit.md.

The main candidate uses data/ThinkMorph/Bagel-SFT-IDVal5, which projects the audited source-coordinate split onto the native BAGEL Parquet without changing images or rendered text. It contains 23,741 train rows and 1,249 validation rows (5%). Epoch mode shuffles every train row deterministically, pads only the three-slot final logical batch, and resumes by update number. Validation uses the EMA model, disables all condition dropout, excludes the 15 distributed tail-padding rows from losses/counts, and runs at step 0 and every epoch. The BAGEL Qwen stack routes packed-training and query-inference calls by their explicit keyword contracts rather than Module.training; EMA can therefore remain in deterministic eval mode while using the loss-producing packed path. Rank-128 LoRA uses replicated DDP, not FSDP: every rank constructs a zero-data meta skeleton, rank zero reads the base once, and DDP broadcasts it; every GPU stores one BF16 frozen backbone and FP32 LoRA parameters. EMA is an FP32 adapter shadow temporarily swapped in for validation, so no second backbone is constructed. Full-parameter paper reproduction remains FSDP. Checkpoint assign restores persistent tensors; the omitted non-persistent rotary buffer is rebuilt before moving rank zero to GPU, while other ranks use to_empty and receive the synchronized state. Condition dropout retains its original behavior. DDP uses find_unused_parameters=true with a dynamic graph; the attempted static-graph optimization was removed after it triggered a reducer assertion during gradient accumulation. LoRA DDP uses activation checkpointing: disabling it reached roughly 137.5/139.8 GiB even at smaller execution packs and exhausted memory again when Adam state was initialized. The stable execution is four samples/rank with accumulation one. If a rank drops every ViT carrier, BAGEL skips the ViT branch for that batch; dynamic DDP then treats its ViT LoRA parameters as legitimately unused. One-hour Delta SIQ attempts write atomic recovery checkpoints every 100 updates and at every validation boundary; wall-clock checkpointing is disabled. The current step-17599 continuation runs on DeltaAI GH200×2 with microbatch 2 × accumulation 4, preserving logical batch 16 and the fixed logical-batch sample cursor. It restores adapter, EMA, AdamW, scheduler and both rank RNG states. Subsequent TIMEOUT retries prefer the continuation's own newest complete checkpoint. Attempts use the two-hour ghx4-interactive queue. Its config is OURS-Bagel/config/sft/thinkmorph_lora_r128_constant_val5_16epoch_2gpu_resume17600.yaml; it resumes the four-GPU continuation's latest complete checkpoint 0017599 and continues toward 23,744 total updates, retaining the parent TensorBoard history. After the A6000 trial OOMed before publishing a checkpoint, the original frozen GH200×2 run was re-enqueued in the same output directory. Its newest committed checkpoint is 0019599; it resumes at update 19,600 with GPU-resident EMA. The 16-epoch continuation config OURS-Bagel/config/sft/thinkmorph_lora_r128_constant_val5_16epoch_4gpu_continue.yaml extends the completed 10-epoch checkpoint 0014839 to 23,744 total updates. It uses four GH200s, two samples per rank and two accumulation microsteps. Epoch extension requires a completed constant-LR parent at its final update; seed and all mathematical settings stay identical. Explicit hardware migrations may adjust processes, samples per pack and accumulation while preserving logical batch 16; added ranks use their seeded RNG when no parent-rank RNG exists. It restores student/EMA/optimizer/scheduler/RNG state, inherits TensorBoard history, and writes new checkpoints in its own parent-linked run directory.

Lab continuation uses /mnt/ultracube/tac010/MMCoT, shared by simiao and luogeng, and the private shared environment /mnt/ultracube/tac010/apps/miniforge3/envs/ThinkMorph. The A6000×8 config thinkmorph_lora_r128_constant_val5_16epoch_luogeng_a6000x8.yaml resumes checkpoint 0019599, with train microbatch one and accumulation two (logical batch 16). Validation uses one sample per rank while retaining all 1,249 rows and padding masks. distributed.ema_cpu_offload=true keeps the FP32 EMA shadow and validation student backup on CPU; updates retain the same EMA equation and checkpoint keys. NCCL loading stages one adapter tensor at a time on CUDA. This frees about 2.7 GiB per rank; GPU-resident EMA at microbatch one reached 47 GiB and OOMed on the first A6000 batch. CPU EMA passed the first updates but later also OOMed, so this configuration is not validated for the full run. Hardware continuation may change validation pack size and EMA placement. Transfer only the BAGEL source closure, base weights, IDVal5 cache, selected checkpoint and parent metadata/TensorBoard through the EmergentTwilight/MMCoT bucket. thinkmorph_sft.relocate_bagel_cache rebases copied JSON path metadata and retains its original files. Prepare a parent-linked source snapshot on the lab filesystem and execute its frozen script through luogeng's gpuq with eight exclusive GPUs; Slurm directives are inert when that script runs in Bash.

Independent OPD replicas stage the originating run's immutable source closure under experiments/cache/ through a task-scoped HF bucket prefix, leaving the lab's live source tree unchanged. Prepare a BAGEL snapshot from that staged source project and record env SPATIAL_MMCOT_PROJECT_ROOT=/mnt/ultracube/tac010/MMCoT in the launch argv, so models/data still resolve from the canonical lab project while SPATIAL_MMCOT_SOURCE_ROOT remains the new frozen source. Use the explicit /mnt/ultracube/tac010/apps/miniforge3/envs/ThinkMorph prefix: a separate empty /mnt/ultracube/tac010/envs/ThinkMorph can shadow a name-only mamba invocation. Luogeng's approved http://webproxy.ucsd.edu:3128 HTTP(S) proxy is required for Hub/judge access; pin it per task, without changing scheduler/global settings. Transfer judge credentials only over SSH into the ignored, mode-0600 key file; keep them out of buckets, snapshots, argv and logs.

A same-start replica copies the original step-zero checkpoint (including student/EMA/reference and all four rank RNG states), not the active run's newest checkpoint. lab_replica.json records source provenance, GPU allowlist and the future gpuq argv. prepared_not_queued means no GPU task has been submitted; only an explicit later launch should execute that argv. Four-GPU replicas keep logical batch 16 and use --gpu-count 4 --gpus 0,1,2,3; the queue owns device visibility. The cluster run is independent and remains untouched.

For a two-GPU launch of an unstarted replica, use config/opd/thinkmorph_igopd_nec_all_2gpu.yaml; only trainer.processes changes from four to two. The dynamic queues still collect 16 fresh trajectories per optimizer update and backpropagate one trajectory at a time, so no separate accumulation or batch-size change is needed. Prepare a new parent-linked snapshot and invoke thinkmorph_sft.prepare_opd_initial_world_change before queueing. It rejects every config difference except process count, rejects trained/Adam-updated checkpoints, preserves student/EMA/reference/optimizer/ scheduler tensors, and copies parent RNG ranks 0/1 into the two-rank checkpoint. Only the checkpoint world/config identity is rewritten, with provenance in initial_world_change.json. This initializer does not relax ordinary runtime resume checks or support world-size changes after training has started. Use gpuq --gpu-count 2 --gpus 0,1; retain the unstarted four-GPU preparation.

If this two-GPU launch is stopped during initial validation before any optimizer update, the untouched four-GPU preparation can be activated again. Before reusing validation bundles, hold writer locks, require both latest checkpoints to be step zero, compare every checkpoint field/tensor except world/config identity, verify matching core-source hashes and config fields except process count, and validate each sample/seed identity. Copy bundles and judge diagnostics only into an absent target validation directory; preserve the source and the original four-rank RNG files. Record this cache import in validation_reuse_from_2gpu.json. This is reuse under an identical initial policy, not permission to rewind a trained run or relax runtime resume guards.

projects/ThinkMorph-edited supplies pinned architecture, packer and FSDP execution code. It accepts an external dataset registry and both EMA-only Bagel-7B and model-only ThinkMorph-7B initialization. Experiment configuration, protocols, preparation, launch and artifact publication belong only to OURS-Bagel.

Canonical BAGEL on-policy distillation

bagel_mmcot.opd implements docs/modeling.md §4. The strict schema and entry point are config/opd/thinkmorph_joint.yaml and python -m bagel_mmcot opd train --config ... --output-dir .... Default initialization is models/ThinkMorph-7B/model.safetensors; original BAGEL uses model.path=models/Bagel-7B-MoT, model.weights=ema. An optional canonical SFT adapter may initialize the student; its rank/alpha and target scope must match. LoRA is FP32 over a BF16 backbone and the VAE stays frozen.

  • Text targets use exact sampled IDs, including model-owned EOS. Full-vocabulary JSD, forward KL and reverse KL are selectable; positive log-ratio clipping shapes the detached EMA target. Vocabulary projection is checkpointed by target-token chunk rather than dropping support mass.
  • Flow targets use detached (x_t,t) recorded before selected steps of the student's actual reverse Euler solve. Selection is uniform without replacement and seeded independently of the solver. Targets use signed, unclipped teacher velocity extrapolation and coordinate-mean MSE. v1 validates raw data-to-noise velocity, Euler and CFG scales one explicitly; guided inference fields and alternative solvers are not implemented.
  • All four views preserve query carriers, block layout and RoPE positions. Scoring reconstructs the complete packed prefix; hidden text payload keys and both VAE/ViT generated-image payloads are zeroed and masked at every layer. Structural marker pieces remain visible. Frozen VAE posterior samples use a content-derived seed/cache, shared by rollout and all scoring views. Learned vision/projection features are recomputed with the active parameter view.
  • Necessity uses student 11; sufficiency uses student 01 and excludes events without earlier generated blocks. Components reduce event means within a trajectory, then means over globally accepted trajectories with that component.
  • Reference is the run-start student adapter, persisted independently of EMA. It evaluates accepted event unions once, always full-visible and unprivileged, with the same sampled prefix/flow state. Direction means are equally weighted over the fixed enabled directions. Reference weight zero disables this term.
  • Routing modes are all, correct_only, privileged_wrong. Only paired teacher views receive answer privilege; student and reference never do. Verifiers use normalized exact answers or rule-prefetched DeepSeek semantic equivalence. Presentation-only outer answer/math/boxed wrappers and comma spacing normalize locally; leading decimal points remain significant. This does not infer an answer from full reasoning, inspect images, or simulate navigation paths. API errors remain errors, never silently converted to incorrect labels.
  • Replay modes are off, uniform, failure_first. Its fixed global budget uses current failed indices first when requested, then samples outside all current raw indices. Replay rereads complete native GT, with unweighted text CE and flow MSE globally normalized by their own target counts. Condition dropout is disabled. Its logit-normal forward noising is separate from OPD's saved reverse states.

OPD uses two dynamic queues per frozen logical batch:

  1. GPU workers atomically claim prompts and commit each trajectory/verdict.
  2. After every logical slot completes, determine routing, component/trajectory denominators and fixed-budget GT replay in logical-slot order. Native replay packs are counted before backward; their deterministic reconstruction is checked against those counts.
  3. One shared backward queue contains whole trajectory-scoring tasks and whole GT-replay tasks. Any optimizer rank can read another worker's committed trajectory and claim the next task. Event/query losses are backpropagated serially within a trajectory to bound activation memory.
  4. Only after every backward task completes, SUM globally normalized gradients across bounded buckets, clip once, apply AdamW once, then update EMA once.

trainer.processes is the number of GPUs/optimizer ranks. Logical batch remains 16 in the canonical config; it need not divide the rank/worker count, and a rank may receive zero tasks. rollout.workers_per_gpu and evaluation.workers_per_gpu include the main rank and default to one. Values above one spawn persistent inference-only replicas on that rank's assigned GPU; their student adapters refresh through shared CPU buffers before each phase. They do not join NCCL or own optimizer/EMA/reference state. Backward uses one replica per GPU to bound memory, with dynamic ownership across GPUs. Extra inference replicas retain their backbone allocations during backward: worker counts must fit the combined peak, not just standalone inference memory.

Python, NumPy and Torch randomness is scoped to sample/update/phase rather than worker identity, including the native GT packer's logit-normal time draws. Queue completion order cannot alter failure-first selection or normalization. Floating-point accumulation order can still differ; numerical agreement, not cross-hardware bitwise identity, is the reproducibility contract.

Queue claims are attempt-local FileStore state. Workers wait on file-store completion/failure markers before entering collectives, so a healthy long query does not consume a collective timeout. scheduling/ exposes the latest attempt's task owners/timings in logical order; _runtime/<attempt>/audit/ retains each attempt's original scheduling evidence. Reference remains frozen. Losses, coverage, clipping and fixed validation go to TensorBoard and per-update JSON. Each sample first atomically saves text IDs, images and solver states with verdict: null, then atomically finalizes the same bundle after valid judging. Retries reuse that pending trajectory and rerun only the judge; committed verdicts remain immutable. A pending/unverified trajectory cannot enter backward. Complete checkpoints include optimizer/scheduler, student, EMA, reference identity/weights and rank RNG; an initial checkpoint precedes rollout. checkpoint_every=1 is required: every reusable query batch must have a committed behavior policy. Recomputing several uncheckpointed updates under a different task order could change floating-point weights and invalidate later saved trajectories, even when their update numbers/seeds match. Identical argv resumes the newest complete in-run checkpoint, rebuilds fresh queues and reuses committed queries with matching behavior update/sample/seed. An interrupted backward discards partial gradients and reruns the whole uncommitted update; it never skips tasks because a previous attempt claimed them. Scheduled validation interrupted after its checkpoint is completed before the next training batch (including validation at the final update).

New experiments use submission-time BAGEL snapshots and SIQ for interactive partitions. config/opd/thinkmorph_joint_a40_smoke.yaml reduces rank, images, solver/query budget and logical batch for a runtime check. The wrapper thinkmorph_sft.opd_resume_smoke invokes identical argv twice and verifies reference immutability, optimizer progression and nonzero native components. GPU execution evidence is recorded in its smoke_report.json; successful CPU tests alone do not establish GPU or learning quality. thinkmorph_dynamic_a40_smoke.yaml runs two optimizer ranks with a smaller logical batch for queue verification. The same wrapper's --interrupt-collection option deliberately terminates only its own process group after a committed query, then checks identical-argv query reuse, distinct backward tasks and checkpoint progression. The deterministic CPU suite also checks skewed queues, peer failure, persistent worker refresh, and logical-16 distributed-versus-serial gradient agreement. The A40 smoke 20260929-162430_joint_text_flow_nec_suf_reference_replay_a40 completed two updates/four fresh rollouts across two identical launches. It restored EMA counter 1 and immutable reference counter 0, then verified the reference tensors remained exactly equal at update 2. Text nec/suf and flow nec were nonzero; pre-clipping norms were 0.02385 and 0.47283, with no clipping and 29.27 GiB peak allocation. Each sampled trajectory had one generated image, so T→V sufficiency was correctly absent; its multi-image masks/gradient/query semantics have CPU regression coverage, not real multi-image GPU evidence yet. All four answers matched the exact verifier, so this smoke exercised replay's batch-external fallback; incorrect/invalid routing and failure-first selection are covered separately by deterministic tests. These are runtime checks, not evidence of accuracy or image-quality gains.

Necessity-only routing comparison and memory smoke

config/opd/thinkmorph_igopd_nec_all.yaml and thinkmorph_igopd_nec_privileged_wrong.yaml differ only in name and routing. Both disable sufficiency without renormalizing the remaining weights: T→V and V→T necessity each have weight 0.25; guidance alpha is respectively 0.25/0.5. They use LoRA-r16, LR 2e-6, logical batch 16, uniform replay budget 4 with weight 0.01, frozen-reference weight 0.1 and EMA decay 0.995. all admits every structurally valid complete trajectory, including wrong answers; privileged_wrong adds the verified GT answer only to wrong trajectories' paired teacher views. Invalid/truncated trajectories enter neither OPD branch. Student, reference, and validation rollout never receive answer privilege.

The text cap is 4,096 new tokens per text block, not per trajectory; max_images=5 counts generated images. The separate 16,384-position context guard remains in place, so these are independent stopping bounds, not a promise to generate five images plus six full-length text blocks. Images retain native 512–1,024 geometry, and raw Euler sampling retains 49 intervals/four queries. Every 16 updates, the current student autonomously rolls out the same fixed 128 held-out queries and the verifier checks final answers. This is rollout accuracy/validity, not teacher-forced token accuracy or validation loss. Current validation inherits the training rollout temperature/sampling/limits; there is no separate greedy-validation switch. Step-zero and final validation also run; identical-argv retries reuse committed validation samples.

For the A100×4 30-minute memory check, thinkmorph_igopd_nec_a100_smoke.yaml changes only the run name, horizon to one update, validation count to four and trainer.memory_profile=true from the privileged configuration. It retains logical16, r16, full image resolution, solver budget and long rollout caps. The entrypoint python -m thinkmorph_sft.opd_memory_smoke --config ... --output-dir ... reports all ranks' allocation/reservation peaks, phase errors, actual generated length/image counts and which necessity components executed. Profiling records live state sizes and CUDA peaks under memory/rank-<rank>-<attempt>/; it is disabled in production and preserves lifetime peak metrics when enabled. Natural short-rollout success does not establish worst-case 4,096-token/five-image safety. A timeout or an unexercised branch is not reported as a passed memory test.

Robust judging and startup recovery

The DeepSeek fallback puts the equivalence rule in a system message and serializes question/reference/candidate as JSON data. It requests json_object output with one boolean correct field and explicitly disables thinking. The output budget starts at 256 tokens and doubles to at most 1,024 across the existing bounded retry count. Empty/malformed/truncated responses are retried with backoff; HTTP 408/429/5xx and connection failures are retryable, while permanent client errors (such as authentication) fail promptly. Only finish_reason=stop can yield a verdict. Exact, full-string legacy labels such as A., (A) or Answer: B are accepted, but prose containing letters, duplicate JSON keys, string/number booleans and ambiguous answers are rejected.

Every API attempt records the judge protocol, sample, request data, response content (bounded/redacted), finish reason, usage and error class in the sample's adjacent <slot>.judge/ directory. Authorization headers are never recorded; the active API key and key-like strings are redacted. Exhausted retries still abort the uncommitted update, preserving the pending rollout for the next launch. The archived failure's original API response was not saved, so its exact text cannot be reconstructed; formatting and pending-rollout regressions exercise the known failure paths without claiming to reproduce that lost response.

For a verifier failure before the first optimizer update, prepare a distinct source snapshot with --parent-run, then run python -m thinkmorph_sft.prepare_opd_verifier_retry --parent-run ... --run-dir ... --config ... before SIQ enqueue. This requires an unchanged full config/world/reference identity, a step-zero checkpoint whose student/EMA/reference are equal, no Adam update state, complete rank RNG files and an inactive parent writer. It copies that state byte-for-byte and validates each cached query's split/slot/sample/ seed/behavior identity. Generated trajectories are reused as pending; old judge decisions and validation aggregates are deliberately not carried over to the repaired judge protocol. verifier_retry.json records the lineage and selected samples. The parent remains untouched. Later SIQ TIMEOUT retries use the child's ordinary newest complete checkpoint and committed query cache. This narrow startup repair does not authorize changing a trained run's judge mid-experiment.

Each GPU stores one frozen BF16 backbone, student FP32 LoRA and its gradients/ Adam states. EMA and reference have independent FP32 LoRA shadows on CPU; a no-grad teacher/reference call temporarily loads that shadow and restores the student afterward. No second full teacher backbone or teacher autograd graph is retained. For the native ThinkMorph-7B checkpoint and scope=mot,r=16, header-derived estimates are: backbone 27.21 GiB, VAE 0.16 GiB, student LoRA 0.30 GiB, gradients 0.30 GiB, Adam moments 0.60 GiB (about 28.6 GiB fixed GPU storage), plus one temporary 0.30 GiB adapter view. Each CPU shadow is 0.30 GiB. Activations, KV caches, vocabulary chunks, attention masks, NCCL and allocator workspace are additional. These are estimates, not measured peaks; A100×4 replicates this footprint on each card instead of pooling four cards' memory.

Legacy native ThinkMorph-7B OPD prototype

ThinkMorph-OPD/ contains the earlier native models/ThinkMorph-7B backbone, protocol tracer, and V→T-only LoRA OPD trainer. It imports BAGEL/MoT architecture code from projects/ThinkMorph-edited but never substitutes models/Bagel-7B-MoT weights. The first real trace, exact token IDs, heterogeneous VAE/ViT/diffusion carriers, controller state machine, and LoRA boundary are recorded in docs/research/20260910-222300_thinkmorph7b_native_token_and_state_machine.md. It is retained for evidence and migration tests; new OPD changes wait for its audited move into OURS-Bagel.

The first native objective selects every complete generated-image→next-text block and implements Necessity only. EMA C11 includes both the generated clean-VAE and ViT carriers; EMA C10 creates shape-matched carriers without encoding RGB and marks every VAE/ViT payload KV invisible to target attention. It preserves <|vision_start|>, <|vision_end|>, logical carrier length and the two image RoPE steps. Both views receive the same answer privilege only for an incorrect rollout. They score the unchanged sampled text block, including its ChatML end target. A positive C11−C10 log-ratio shapes the C11 teacher target, which is distilled to the full-visible student with symmetric JSD.

Scoring reconstructs historical carriers under stop-gradient, then retains the student graph through the current generated image and target block. The base adapter covers understanding and _moe_gen attention/MLP linears; SigLIP, VAE, connectors and output heads stay frozen. Its support is the deduplicated union of C11/C10 top-128 tokens, with retained-mass checks and the same per-coordinate 0.05 clipping semantics as the public Visual-OPSD implementation.

The DeepSeek verifier is rule-prefetched and fail-closed. Current-update wrong or invalid sample indices fill the fixed replay budget first; any deficit is uniformly filled outside the raw rollout batch. Replay re-reads the independent GT sample and reconstructs the original ThinkMorph training plan: observation VAE+ViT, instruction, GT text-1, noised GT-image rectified-flow target, the same image as clean VAE+ViT context, and GT text-2. It applies length-reweighted text CE plus visual-position-mean flow MSE, and never pairs a wrong sampled image with GT continuation. T→V remains absent from OPD but is preserved by replay.

configs/v2t_opd_base.yaml uses logical batch 16 on four GH200 ranks, 128 updates (2,048 fresh rollouts), rank/alpha 16, rollout temperature 0.75, LR 5e-6, EMA 0.995, JSD β/temperature 0.5/1.0, contrast alpha 0.5, and replay 4/16 with outer coefficient 0.01 and text/flow weights 1/1. These values and their evidence status are audited in docs/research/20260910-225127_thinkmorph_native_v2t_opd_hyperparameters.md. After this 2,048-rollout pilot passes its stop criteria, configs/v2t_opd_main.yaml is the recommended main experiment: 1,000 updates and 16,000 unique task-proportional rollouts, retaining all objective/replay settings while using the official-quality 50-step flow sampler, 50-update warmup, validation every 50 updates and recovery checkpoints every 16 updates. The trainer writes every rollout atomically, synchronizes globally normalized LoRA gradients with one flattened collective per dtype instead of hundreds of per-tensor collectives, checkpoints optimizer/scheduler/EMA state, and resumes in the same prepared run directory.

The revised GH200×4 smoke completed 4 updates/16 rollouts in 5m06s, including attention-masked C10, both-expert LoRA/EMA, full text+flow replay, flattened gradient SUM, start/end validation, per-update checkpoints, TensorBoard and student/EMA adapters. Peak allocated memory was 79.19 GiB and all gradient norms were finite without clipping. Its replay coefficient 0.1 was numerically too large relative to raw OPD, so canonical pilot/main configs use 0.01. No pilot or main experiment was submitted.

Paper-aligned BAGEL baseline

The execution fork is projects/ThinkMorph-edited, pinned from upstream commit c1a48ad; OURS-Bagel owns the baseline and future BAGEL objectives. This is separate from the UniAR conversion below. The starting model is the pinned models/Bagel-7B-MoT EMA checkpoint, and its isolated DeltaAI environment is ThinkMorph (Torch 2.7.1+cu128, Transformers 4.49.0, FlashAttention 2.8.3.post1).

data.convert_thinkmorph converts all four released datasets to the official three-column BAGEL contract under data/ThinkMorph/Bagel-SFT. It preserves the official system prompt and tag strings byte-for-byte, re-encodes both images as PNG, writes 100 rows/file and 10 rows/row-group, and publishes a hash manifest, parquet_info.json, and READY atomically.

OURS-Bagel/config/sft/thinkmorph_paper_bagel7b.yaml is the canonical four-GH200 baseline: 8,000 optimizer updates, LR 1e-5, cosine with 2,000-step warmup, CE/MSE weights 1, condition dropout 0.1/0.3/0.3, 32K carriers per rank microstep, four microsteps per update, BF16 four-way FULL_SHARD, activation checkpointing, full-parameter AdamW, EMA 0.9999, and clip norm 1. The accumulation window uses one global CE denominator and one global MSE denominator. The smoke entrypoint keeps the 8,000-step scheduler but stops after 20 committed updates.

The primary-source audit and the known defects in the public launch script are recorded in docs/research/20260905-140823_thinkmorph_official_sft_reproduction_contract.md.

DeltaAI environment

MAMBA=/work/nvme/bgjw/tcheng2/apps/miniforge3-deltaai/bin/mamba
$MAMBA create -n ThinkMorph python=3.12 pip setuptools wheel ninja -y
$MAMBA run -n ThinkMorph python -m pip install 'torch==2.7.1+cu128' \
  --index-url https://download.pytorch.org/whl/cu128
$MAMBA run -n ThinkMorph python -m pip install --no-deps \
  'torchvision==0.22.1' --index-url https://download.pytorch.org/whl/cu128
$MAMBA run -n ThinkMorph python -m pip install \
  -r projects/ThinkMorph-edited/requirements-gh200.txt

Install FlashAttention 2.8.3.post1 from an SM90/Torch-2.7-compatible wheel or build it on a GH200 using the CUDA 12.9 procedure in docs/uniar.md. The current isolated environment passes pip check and imports Torch 2.7.1+cu128, Transformers 4.49.0, and FlashAttention 2.8.3.post1.

The matching x86 environment on Delta is /work/nvme/bgjw/tcheng2/apps/miniforge-delta/envs/ThinkMorph; it has the same Torch, torchvision, Transformers, and FlashAttention versions. Do not reuse the DeltaAI AArch64 prefix on Delta.

VLMEvalKit UniAR evaluation

projects/VLMEvalKit_Thinkmorph_llm is the canonical evaluation fork and has a separate ThinkMorph-Eval environment under both Miniforge installations. It uses Torch 2.7.1+cu128, Transformers 4.57.0, PEFT 0.20.0, FlashAttention 2.8.3.post1, and the fork's evaluation requirements. pip check and the full VLMEvalKit configuration import must pass on both architectures.

The registered model keys are uniar-sft, the rank-64 two-/four-epoch development adapters, the two-epoch BitCE weight-1/weight-3 ablations, and the two-epoch absolute-IG adapter uniar-thinkmorph-v1-ig-abs-r64-val5-2epoch. The adapter injects only the canonical training protocol as the system prompt. It preserves the VLMEvalKit image/text user message exactly, with no appended instruction, uses the canonical full-prefix interleaved controller, and returns the readable assistant sequence. Set SPLIT_THINK=1 so MCQ scoring receives only the post-thought final segment. VSP is the exception: inference retains the full assistant response even when SPLIT_THINK=1, because its upstream scorer extracts the boxed path from the reasoning trace. Prompt-controlled VSP studies use the registered VSP_maze_task_main_original_60_balanced dataset (ten deterministic examples per maze size 3--8). Model keys ending in vsp-thinkmorph-short or vsp-short replace only the framework-owned VSP user content with the exact training prompt and its final test image; they retain the UniAR system protocol, chat template, generation path, and upstream strict scorer. VSP scoring defaults to the fork-compatible thinkmorph_strict mode. Set VSP_VALIDATION_MODE=official to reproduce the official FrozenLake transition semantics: off-grid actions leave the state unchanged and reaching the goal terminates successfully. Mode-qualified result filenames prevent cache reuse across scoring definitions. Native models/ThinkMorph-7B uses thinkmorph-7b-vsp-short-a40 for the same short-prompt VSP protocol; its A40-safe model-side batch is four. BLINK-Jigsaw is cached at data/VLMEvalKit/BLINK_Jigsaw.tsv; its MD5 is 174fc5d2ed0d67f9e0b33cb8a8be6727 and it contains 150 rows.

Canonical LLM-assisted evaluation uses the fork's original verifier with DeepSeek V4 Flash as its fallback choice extractor:

LMUData=data/VLMEvalKit SPLIT_THINK=1 torchrun --standalone \
  --nproc-per-node=4 projects/VLMEvalKit_Thinkmorph_llm/run.py \
  --data BLINK_Jigsaw \
  --model uniar-thinkmorph-v1-vanilla-r64-val5-2epoch \
  --judge deepseek-v4-flash --reuse --work-dir <prepared-run>

The completed canonical DeepSeek-V4-Flash-judged results supersede all scores from the retired local exact matcher:

Epoch Bit CE weight IG BLINK-J VStar overall (direct/relative) MMVP SAT overall (action/ego/goal/object/perspective)
0 10 none 58.67 60.73 (59.13/63.16) 72.33 44.67 (54.05/43.48/58.82/26.09/33.33)
2 1 none 57.33 59.69 (58.26/61.84) 75.00 48.67 (56.76/69.57/52.94/30.43/33.33)
2 3 none 58.00 59.69 (56.52/64.47) 73.67 52.67 (56.76/60.87/76.47/43.48/24.24)
2 10 none 52.67 60.73 (56.52/67.11) 73.33 46.67 (51.35/56.52/70.59/30.43/21.21)
4 10 none 51.33 62.30 (60.87/64.47) 70.67 45.33 (51.35/56.52/70.59/26.09/18.18)
2 10 absolute IG 46.00 59.69 (57.39/63.16) 74.33 46.00 (48.65/69.57/58.82/26.09/27.27)

All values are percentages. BLINK-J has 150 rows, VStar 191, MMVP 300, and SAT circular scoring reports 150 paired groups.

When an evaluation is queued before its adapter is committed, bind the SIQ task to the training SIQ Task ID with afterok and set WAIT_FOR_ADAPTER_DIR to the expected adapter directory. The evaluator waits for its atomic COMPLETE marker before loading weights; resumable --reuse keeps a retried bundle in the same result directory.

The same frozen evaluator runs other VLMEvalKit MCQ datasets by setting DATASET_NAME; it leaves each framework-generated user prompt unchanged. Versioned task evaluations live under experiments/output/uniar-sft/<dataset>/v1/runs/eval/. On Delta, A100 evaluation uses one model rank per GPU; A40 evaluation may set VLMEVAL_WORKERS_PER_GPU=2 and launch eight ranks on four GPUs. Shared-GPU ranks use Gloo only for barriers, while inference and result shards remain rank-local.

Canonical UniAR task evaluation sets UNIAR_ROLLOUT_DIR and atomically saves one rollouts/<dataset>/<sample-id>/ directory per generated answer. Its rollout.pt contains prompt, assistant and full token IDs plus observation and generated visual codes; metadata.json contains the unchanged framework message, decoded event stream, grids, stop/cycle metadata and workbook-facing artifact identity. RGB image decoding is deferred. A COMPLETE marker is the artifact commit boundary, and the workbook extra_records cell links back to the committed directory.

The runner fixes VLMEVAL_EVAL_ID=RUN for the lifetime of a prepared eval run; otherwise upstream's date/live-Git identity can change across SIQ retries and strand partial rank pickles in an older directory. It also sets VLMEVAL_DUMP_EVERY=1, so every completed sample updates its rank pickle. These are persistence controls only and do not modify generation, dataset.evaluate, or the upstream verifier. Canonical multi-rank inference uses VLMEVAL_SCHEDULING=dynamic: ranks claim the next pending row from one distributed Store counter, commit after every sample, and merge all prior rank shards before queue initialization. Static scheduling remains selectable for controlled continuations. When changing evaluator world size or hardware, seed a new parent-linked run with thinkmorph_sft.seed_vlmeval_resume: it copies committed rollout and completed benchmark artifacts, merges old rank shards into dataset PREV.pkl files, and rewrites workbook rollout links to the new self-contained run. When the canonical output path must remain stable, create the changed execution snapshot under <eval-run>/_continuations/<timestamp>/ and keep its OUTPUT_DIR pointed at the parent eval run. The nested snapshot and parent record preserve execution provenance while results and rollouts stay canonical.

The v2 BitCE-weight-3 long control exposes exact epoch-boundary evaluation keys for epoch 1 (step-001524), epoch 2 (step-003048), and epoch 3 (step-004572); all load the nested checkpoint adapter/ while validating the checkpoint-level COMPLETE marker. The lower-LR (1e-5) eight-epoch control exposes epoch 3 (step-004572) and step-009144 through dedicated evaluation model keys. The Grid16 lower-LR control exposes epoch 6 (step-009144) as uniar-thinkmorph-v2-r64-grid16-bitce-lambda3-lr1e-5-epoch6. The GH200-resumed Grid16 lower-LR control exposes epoch 7 (step-010668) as uniar-thinkmorph-v2-r64-grid16-lr1e-5-resume1100-epoch7. The matching 12-epoch continuation resumes its completed step-012192 state on four Delta H200s with the unchanged per-rank microbatch 4/global batch 16; four additional epochs end at step 18,288. The LLM fork preserves upstream VLMEvalKit verifier and option-prefetch logic; do not port the stricter local exact-match verifier into it. Its external judge is deepseek-v4-flash through the official OpenAI-compatible endpoint, using DEEPSEEK_API_KEY; judging defaults to non-thinking mode. API_KEY is an ignored local convenience file and must never enter Git or source snapshots.

Native models/ThinkMorph-7B task evaluation uses model key thinkmorph-7b-local. Its batch-8 adapter keeps static rank sharding, writes intermediate generated images under the eval run's generated_images/, and uses the same stable result namespace and DeepSeek verifier.

The BAGEL rank-128 ten-epoch control uses model key bagel-thinkmorph-r128-val5-10epoch. It reconstructs the audited 556-target LoRA schema on models/Bagel-7B-MoT/ema.safetensors, loads the final EMA adapter, and prefixes the unchanged benchmark question with the exact training system prompt after its input-image carrier instead of adding the native pre-image prompt. Use model-side batch one with the dynamic queue when sharing each GH200/H200 between two evaluator workers. The matched unfine-tuned baseline uses bagel-7b-mot-original: it loads the released BAGEL EMA weights with the same protocol, decoding and benchmark scoring, but does not inject an adapter. For the canonical VSP comparison use their -vsp-short model-key variants with VSP_VALIDATION_MODE=official. These use the exact ThinkMorph short custom prompt and only the final test image. The suffix affects VSP prompts only; the other five benchmarks retain their standard dataset prompts. Prompt variants use separate run/model namespaces to avoid prediction reuse.

UniAR conversion

Training corpus

The public ThinkMorph SFT corpus under data/ThinkMorph/ contains 24,990 train-only rows: Chart Refocus 6,000, Jigsaw Assembly 6,000, Spatial Navigation 6,000, and Visual Search 6,990. Each row has one problem image, two textual reasoning segments, one intermediate reasoning image, and one answer. Public training rows are not repurposed as a held-out test split.

thinkmorph_sft.prepare_cache converts every row to the canonical spatial_mmcot_trajectory_v1 and zebra_uniar_cache_v1 contracts. The exact event order is system prompt, user question and observation, then assistant thought, generated state, thought, and final answer. Rendering is owned by the same v2 ChatML path as Zebra-CoT, including exactly one newline between assistant events and the student-owned <|vision_start|> boundary. Generated images are encoded into UniAR BSQ targets; raw ThinkMorph records remain immutable.

The full-data cache is data/ThinkMorph/ThinkMorph. All its manifest rows have split train; it is unchanged by development splitting. Full-data training disables internal validation. Configuration/epoch selection instead uses the fixed data/ThinkMorph/ThinkMorph-IDVal5 development view described below. Final task evaluation uses external benchmarks, not training membership.

Fixed 16×16 generated-state cache

thinkmorph_sft.prepare_grid16_cache builds the parallel all-train cache data/ThinkMorph/ThinkMorph-Grid16 and then projects the existing IDVal5 assignments onto data/ThinkMorph/ThinkMorph-Grid16-IDVal5. It never modifies the original cache or recomputes validation membership.

For every assistant-generated target, the source RGB image is resized with preserved aspect ratio until its longest edge is 512 pixels, centered on a 512×512 RGB (128,128,128) canvas, and then encoded by the canonical UniAR BSQ encoder. The resulting patch grid is always 32×32 and its composite BSQ grid is always 16×16. User-query images are not passed through the historical 960px cap or the extra qwen-vl-utils resize stage; they go directly through the checkpoint image processor, matching the intended understanding path and its declared min_pixels=4096, max_pixels=16777216, patch size 16, and merge size 2.

OURS/config/sft/v2/thinkmorph_vanilla_r64_val5_grid16_bitlambda3_lr1e-5_8epoch.yaml is byte-for-byte equivalent in model, loss, optimizer, schedule, batching, validation, and duration to the prior lower-LR eight-epoch control, except for its run identity and Grid16 development-cache path.

UniAR v1 vanilla configuration

OURS/config/sft/v1/thinkmorph_vanilla_base.yaml is the train-only UniAR counterpart of the v1 Tetris vanilla base, not the separate BAGEL baseline. It uses all 24,990 cached rows without a validation/test split or prefix subset, one epoch, half-sample trajectory/update-uniform BitFlip in [0, 0.1], IG/EMA disabled, and the same text/bit weights, rsLoRA, optimizer, scheduler, and clip norm 5. Its current rank is 64 (Tetris's original base uses 32). Four-GPU DDP with microbatch 4 and accumulation 1 gives nominal logical batch 16. TensorBoard and JSONL log every update; update/wall checkpoints remain enabled.

evaluation.enabled=false, evaluation.at_start=false, evaluation.samples=0, and data.validation_splits=[] disable startup, periodic, resumed-step, and final internal validation. No test job is implied. The Zebra-specific probe IDs/provenance are cleared; this config targets sft train, not the Zebra-small sft inspect/probe diagnostics.

UniAR v2 constant schedule

OURS/config/sft/v2/ carries the long-horizon configuration family. Its base files retain the v1 model, data, objective, optimizer, batching, evaluation, checkpoint, and logging settings, while switching to constant LR after a fixed update-count warmup. ThinkMorph uses 50 warmup updates; the value is independent of the configured epoch count.

thinkmorph_vanilla_r64_val5_bitlambda3_16epoch.yaml is the first v2 development run: rank 64, fixed IDVal5 split, 16 epochs, Bit CE weight 3, vanilla SFT with IG/EMA disabled, logical batch 16, and clip norm 5. It saves an exact checkpoint at every epoch boundary in addition to the ordinary 100-update and 30-minute recovery points, then runs the complete 1,249-row validation exactly once per epoch. Update-periodic validation is disabled for this config. The 4/8/12/16-epoch states therefore belong to one constant-LR trajectory and can be evaluated without changing the scheduler horizon.

Fixed same-distribution development validation

data/ThinkMorph/README.md declares the two views. The development view has 23,741 training and 1,249 validation rows (approximately 5% held out): Chart Refocus 5700/300, Jigsaw Assembly 5700/300, Spatial Navigation 5700/300, and Visual Search 6641/349. Whole-group and subtype rounding takes precedence over forcing exactly 1,250 rows. This is our development split, not an author-held-out validation/test set.

The split builder owns grouping and selection:

  • Preserve source cache IDs and all token/code tensors; only metadata changes. The new view uses the established absolute-shard-path zero-copy contract.
  • Join original-image filename families, exact decoded RGB input and target images, and cross-task image matches into connected groups. Reconstructed jigsaw targets protect against different shuffles of the same source image.
  • Natural-image resize/re-encoding checks use a conservative 128-bit dHash and thumbnail-difference rule. Sparse charts/mazes are excluded from that fuzzy rule to avoid grouping distinct diagrams merely because templates match.
  • Stratify whole groups by task and available subtype: puzzle grid, maze size, and chart/search source type. A seeded whole-group subset targets 5% of rows per stratum; cross-stratum connected groups stay intact.
  • Neither answer labels, model losses, nor evaluation outcomes select the membership. There are 28 multirow groups and 16 recorded near-duplicate linking edges. Source identity, decoded-pixel identity, and group leakage audits all pass. This does not prove absence of all arbitrary crops or semantically equivalent rerenders without retained source identity.

SPLIT.json records policy, source hashes and the manifest checksum; image_groups.jsonl records image/source/group/subtype provenance. The fixed manifest SHA256 is 6fc8ab060cd29efebe303c4a9c0660271d6e8f5c69c49375a466aa2f04c92c1a.

Task-specific v3 SFT uses zero-copy views of this fixed split. The Navigation view is data/ThinkMorph/ThinkMorph-Navigation-IDVal5: it retains exactly the original 5,700 train / 300 validation Spatial_Navigation rows and points to the unchanged tensor shards. Its vanilla configuration is OURS/config/sft/v3/thinkmorph_navigation_vanilla_r64_val5_lr1e-5_8epoch.yaml; only the run identity, cache view, and full-validation sample count differ from the v3 all-task control. Its controlled absolute-IG counterpart changes only the run name, enables validation IG, and applies the established all-event best setting: T→V weight/temperature 0.05/32 and V→T 10/0.25. The Navigation BitCE scale control changes only the vanilla run name and visual BitCE weight from 10 to 3; it retains eight epochs and the same fixed split. The Grid16 Navigation vanilla control instead keeps BitCE weight 10 and all training settings, but uses the zero-copy data/ThinkMorph/ThinkMorph-Grid16-Navigation-IDVal5 view. All 5,700/300 rows preserve the fixed split and use a 16×16 generated BSQ target. The completed epoch-6 (step-002196) and epoch-8 (step-002928) checkpoints are registered for VSP evaluation as uniar-thinkmorph-v3-navigation-r64-lr1e-5-epoch6 and uniar-thinkmorph-v3-navigation-r64-lr1e-5-epoch8. The matching absolute-IG epoch-6/epoch-8 VSP-short keys are uniar-thinkmorph-v3-navigation-ig-abs-best-r64-lr1e-5-epoch6-vsp-short and uniar-thinkmorph-v3-navigation-ig-abs-best-r64-lr1e-5-epoch8-vsp-short. The Grid16 navigation epoch-8 VSP-short key is uniar-thinkmorph-v3-navigation-grid16-r64-lr1e-5-epoch8-vsp-short. Reproduction (only to a new output path; existing views fail closed):

PYTHONPATH=OURS/src:experiments/src mamba run -n UniAR \
  python -m thinkmorph_sft.split_validation \
  --source-root data/ThinkMorph \
  --source-cache data/ThinkMorph/ThinkMorph \
  --output data/ThinkMorph/ThinkMorph-IDVal5 --seed 17 --workers 2

Rank-64 duration selection and full-data final training

The explicitly controlled development configs are:

  • OURS/config/sft/v1/thinkmorph_vanilla_r64_val5_2epoch.yaml
  • OURS/config/sft/v1/thinkmorph_vanilla_r64_val5_4epoch.yaml
  • OURS/config/sft/v1/thinkmorph_vanilla_r64_val5_8epoch.yaml

These are result-producing v1 runs and use run.category: runs; their artifacts resolve below experiments/output/uniar-sft/thinkmorph/v1/runs/sft/.

The 2-epoch visual-loss scale ablation uses thinkmorph_vanilla_r64_val5_bitlambda{1,3}_2epoch.yaml. Each changes only run.name and objective.bit.weight from the rank-64 2-epoch baseline (whose bit weight is 10); text weight remains 1. These are independent runs from UniAR-SFT with their own identical 2-epoch cosine schedules and the full 1,249-row development validation.

thinkmorph_ig_abs_r64_val5_2epoch.yaml is the controlled absolute-IG counterpart of the rank-64 2-epoch vanilla configuration. It enables all-event absolute IG with T→V weight/temperature 0.05/32 and V→T 10/0.25, and records IG during the same complete 1,249-row validation. Every other model, data, BitFlip, optimizer, schedule, DDP, and logging field matches vanilla.

They differ only in run name and epoch count. All use rank 64, rsLoRA alpha 32, the existing LoRA/head learning rates 5e-5/1e-5, uniform half-sample BitFlip, IG/EMA off, global batch 16, clip norm 5, and every-step TensorBoard/JSONL. Each starts from UniAR-SFT independently and uses its own duration's cosine schedule; this is not equivalent to taking epoch-2/4 snapshots of an 8-epoch schedule. No learning-rate change is implied by creating this split.

Internal validation is enabled at startup, every 250 updates, and training completion, with all 1,249 fixed rows. It is clean teacher-forced text/visual validation, not autonomous rollout accuracy. Treat component losses and stability as supporting evidence; task-appropriate answer/visual-operation rollouts and a protected final test remain separate workflows. Do not repeatedly use a final benchmark test for epoch/rank/LR selection.

thinkmorph_vanilla_r64_base.yaml is the matching full-data template: original cache, rank 64, no held-out split and internal validation off. Set trainer.epochs to the selected duration for the final run. It preserves the same model/objective/optimizer settings as the development family. Start the final run from the base model; changing data membership is not an exact resume of a development run. Keep the held-out results from development runs, but do not call those rows held out after final full-data training includes them.

Preparation

The cache builder is multi-GPU, strict about all task counts, and resumable from committed rank manifests. A torn terminal JSONL line is discarded; an existing atomic READY marker makes a retry exit successfully.

torchrun --standalone --nproc-per-node=4 -m thinkmorph_sft.prepare_cache \
  --source-root data/ThinkMorph \
  --model-path models/UniAR-SFT \
  --output-root data/ThinkMorph/ThinkMorph \
  --attn flash_attention_2 \
  --auto-resume

Xet Storage Details

Size:
51.7 kB
·
Xet hash:
3bb1afe067a12da54fb8d5377e29173b47db7d9e74d2ac70bd44b00d15a0085c

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.