train_u1 — simple LoRA trainer for SenseNova-U1-8B-MoT
A single-GPU LoRA / partial-finetune trainer for
SenseNova-U1-8B-MoT.
Drives the entire run from one YAML file. Save format follows the upstream
LoRA convention (<key>.lora_down.weight / lora_up.weight / .alpha) so
checkpoints drop straight into the official inference scripts.
Fits on a 32 GB GPU (RTX 5090 / A100-40 / RTX 6000 Ada). Peak VRAM ~20 GB
on the train dataset at 2048².
What you get out of the box
Config-first: every run is one YAML file (configs/default.yaml).
Per-module rank + enable: each LoRA target (q_proj_mot_gen, mlp_mot_gen.down_proj,
fm_modules.fm_head.0, …) takes its own rank / alpha / on-off independently.
Experimental MoE target grammar: A3B-style generation experts can be
addressed explicitly (gen_moe_mlp, gen_moe_router,
mlp_mot_gen.experts.*.gate_proj) without changing the stable 8B main path.
Default = small-data style baseline: configs/default.yaml uses
x0 + uniform t + no condition dropout, short captions, LoRA on attn+mlp,
and full fine-tuning of the timestep/noise embedders, gen vision bridge, and
fm_head.
Official-alignment recipe is optional: configs/official_alignment.yaml
keeps the public report knobs together for research ablations, but it is not
the safest first run for small style datasets.
Upstream-format save: load straight into examples/t2i/inference.py
via --lora_path, or stack with the official 8-step LoRA.
bf16 training, not 4/8-bit. Earlier 4-bit nf4 LoRA training produced
grid artefacts and limb collapse on the gen tower; switching the base to
bf16 (with offload + static prefix-KV cache) eliminated both.
This is a compatibility layer, not the main training path and not an end-to-end
A3B training claim. The stable release target remains SenseNova-U1-8B-MoT;
A3B training depends on public MoE runtime support that can instantiate the
mlp_mot_gen.experts.* modules.
Before training, estimate MoE LoRA size from metadata only:
Each .txt is a single-paragraph natural-language caption. Put the style
or artist anchor in a stable way and keep style.trigger aligned with how
you will sample later. The default config prepends that trigger to every
caption.
Optional: append a <think>...</think> reasoning label inside the
same .txt after a ---think--- delimiter line:
An illustration by Hayateluc depicting a wisteria-trellis path under
morning glow, painterly composition, no people.
---think---
1. **Instruction Understanding:** ...
6. **Explicit Prompt:** ...
Think labels are ignored by default because low-quality or highly
templated think text can dominate the prefix and hurt style binding. To use
them, set data.use_think_labels: true and evaluate with the same think
distribution at sample time. Do this only when your think labels are
curated and repeatable.
Parquet/arrow shards (recommended for ≥ ~10k images, e.g. 1M
scaling):
bash
1# Pack a folder dataset → single parquet shard2python -m train_u1.scripts.dataset_tools pack-arrow dataset/my_style \3 --out artifacts/my_style.parquet
4# Inspect first 3 rows5python -m train_u1.scripts.dataset_tools inspect-arrow artifacts/my_style.parquet
Schema: sample_id, caption, think (nullable), image (binary). Set
data.data_dir in the YAML to point at the parquet path; the training
script auto-detects .parquet and uses ArrowT2IDataset instead of
PairedFolderT2IDataset.
Edit configs/default.yaml. The only fields you must touch:
Each .safetensors is in upstream format (<key>.lora_down.weight /
.lora_up.weight / .alpha).
Sample
bash
1./sample.sh configs/default.yaml \2 artifacts/my_run/trainable_state.safetensors \3 --prompt "anime girl in dark kimono on a veranda…"\4 --image-h 1024 --image-w 1024\5 --num-steps 50 --cfg-scale 4.0 --timestep-shift 3.0\6 --out preview.png
Optional --think-mode --think-max-tokens 1024 adds a chain-of-thought
window before image generation for prompt-fidelity boosts (+~95 s/sample).
Configuration
Everything below the data path is opinionated but tunable. The full schema
lives in train_u1/config.py.
yaml
1run_name: my_run
23data:4data_dir: dataset/my_style
5cap_max_pixels:4194304# 2048² hard cap per image6snap_bucket:true# snap to upstream bucket grid7use_think_labels:false# keep prefixes short by default8# n_samples: 56 # cap dataset size; default = use everything910style:11trigger:"my style"# prepended to every caption12prompt_template: official # 'official' (recommended) | 'plain'1314lora:15preset: attn_mlp_no_head # attn+mlp LoRA; fm_head is full-FT below16# spec: "attn=r64a64;mlp=r64a64;mlp_mot_gen.down_proj=off"17dropout:0.01819unfreeze:# full-FT (non-LoRA) regex patterns20-'^fm_modules\.timestep_embedder\.'21-'^fm_modules\.noise_scale_embedder\.'22-'^fm_modules\.vision_model_mot_gen\.'23-'^fm_modules\.fm_head\.'2425train:26steps:600027lr:5.0e-528seed:029shuffle:true30grad_accum:131checkpoint_every:60032# Small-data style baseline. See docs/small_data_style_ablation.html before33# switching to the official-alignment recipe.34loss_type: x0
35t_dist: uniform
36t_logit_mean:-0.837t_logit_std:0.838# huber_delta: 1.0 # only used for *_huber39cond_dropout_text:0.040cond_dropout_both:0.04142runtime:43keep_kvs_on_gpu:true44gc_skip_last:645device: cuda
46cpu_device: cpu
47# upstream_lora_path: SenseNova-U1-8B-MoT-LoRA-8step-V1.0.safetensors48# upstream_lora_skip: ['fm_modules.fm_head']
LoRA spec mini-language
lora.spec (or --lora-spec on the CLI) is a ;-separated list of
target=BODY entries. Targets are specific modules or group aliases:
The shipped configs/default.yaml uses attn_mlp_no_head and full-FTs
fm_head separately because that was the most stable small-data baseline in
our ablations. The default preset name inside the LoRA parser still means
"match upstream 8-step LoRA coverage"; use it only when that exact module
coverage is what you want. For report-alignment research, start from
configs/official_alignment.yaml.
Stack with the official 8-step distill LoRA
Upstream released a step-distillation LoRA that brings inference down to 8
NFE at cfg_scale=1.0. You can train your own style LoRA on top of it
by setting runtime.upstream_lora_path in your YAML — at training time we
bake-in the official 8-step delta into the bf16 base (skipping fm_head so
we don't clobber our own fm_head LoRA), then wrap our LoRA on top.
Upstream: OpenSenseNova/SenseNova-U1 (Apache-2.0).
We pin commit df86ca90 of the modeling code and load it via
trust_remote_code after sha256 verification. The training stack here is
independent of upstream training code (none was released).
Model weights: sensenova/SenseNova-U1-8B-MoT (post-RL) and
sensenova/SenseNova-U1-8B-MoT-SFT. Use according to their model card.
8-step distill LoRA: sensenova/SenseNova-U1-8B-MoT-LoRAs — public
release; consumed via the upstream_lora_path mechanism.
This trainer is licensed under Apache-2.0 (see LICENSE).
Thanks to comfy.org for the GPU power support. The open-source community will not forget.