Native diffusion language-model training
Train supported diffusion checkpoints with diffusion_lm: and the usual
Axolotl chat datasets. No plugin is required. The model profile selects its
noise process, attention layout, logit alignment and reference loss.
| Model | Noise and layout | Default native objective |
|---|---|---|
| Nemotron-Labs-Diffusion (3B, 8B) | Absorbing mask, full sequence, aligned logits | Inverse-time weighted CE at masked positions |
| Dream v0 | Absorbing mask, full sequence, shifted logits | Source-compatible masked CE with inverse-time weighting |
| DiffusionGemma | Uniform token noise, causal encoder and bidirectional canvas | Canvas CE, encoder AR CE and two-pass self-conditioning |
Other models can still train with the legacy masked-diffusion objective by
setting diffusion_lm.from_causal_lm: true with the legacy diffusion plugin;
any other diffusion_lm: config on a model without a native profile is
rejected rather than trained as causal SFT. See
Converting a causal LM.
Installation
Native diffusion training runs on Axolotl’s supported Torch range, using the
dependency versions pinned in pyproject.toml. attn_implementation: varlen
additionally requires Torch 2.14, so install the torch==2.14.0 wheel for your
accelerator from PyTorch’s installation guide
before installing Axolotl:
python -m venv .venv-diffusion
source .venv-diffusion/bin/activate
python -m pip install --upgrade pip
python -m pip install torch==2.14.0 # or the accelerator-specific wheel
python -m pip install -e .Put this environment’s bin directory first on PATH so axolotl train
launches the matching Accelerate installation.
Start with Nemotron 3B
The small iteration target is nvidia/Nemotron-Labs-Diffusion-3B. From the
repository root:
axolotl preprocess examples/nemotron-diffusion/lora-smoke.yaml
axolotl train examples/nemotron-diffusion/lora-smoke.yamlThis recipe trains two optimizer steps on 32 synthetic chat records. It checks
preprocessing, packed training, gradient accumulation and adapter saving; it is
not a quality benchmark. It pins the model and remote-code revision with
revision_of_model and uses the published bidirectional diffusion mode,
aligned logits and the existing mask token 100. Padding reuses the tokenizer EOS
token without resizing the vocabulary. When you change models, remove or
replace revision_of_model.
Attention backends
Native diffusion defaults to flex_attention when attn_implementation is
omitted. eager and sdpa remain explicit fallbacks; the examples use sdpa
as a dense reference. FlexAttention requires a compatible GPU, and its first
execution includes kernel compilation.
Nemotron also supports PyTorch’s public variable-length attention:
attn_implementation: varlen
sample_packing: trueThis path gathers valid Q/K/V tokens into THD format and passes cumulative
document offsets to torch.nn.attention.varlen.varlen_attn, without building a
dense document mask. PyTorch selects the underlying kernel. varlen requires
Torch 2.14 or newer, a native full-sequence diffusion model (Nemotron),
uncached bidirectional attention, and zero attention dropout. Dream and
DiffusionGemma keep their existing backends: Gemma’s per-layer
encoder-prefix/canvas visibility cannot be expressed as one full-sequence
varlen call.
DiffusionGemma’s large attention heads can exceed a GPU’s per-kernel shared memory limit with the default Flex tile sizes. Smaller tiles can be configured without changing attention visibility:
attn_implementation: flex_attention
flex_attn_compile_kwargs:
fwd_BLOCK_M: 16
fwd_BLOCK_N: 16
fwd_num_stages: 1
bwd_BLOCK_M1: 16
bwd_BLOCK_N1: 16
bwd_BLOCK_M2: 16
bwd_BLOCK_N2: 16
bwd_num_stages: 1Packed training
Enable sample_packing: true. Logical documents remain isolated even when they
share a physical row, and position IDs restart at document boundaries.
DiffusionGemma also keeps each canvas associated with its own encoder history.
sample_packing: true
sequence_len: 256
micro_batch_size: 2
diffusion_lm:
overflow_policy: error
generate_samples: falsesequence_len limits each example’s logical layout. For packed and
batch_flattening rows, Axolotl derives the total allocation as
sequence_len * micro_batch_size. Ordinary padded batches have no aggregate
row budget. Evaluation uses eval_batch_size when it is set.
FlexAttention rounds capacity down to 128-token buckets. Encoder/canvas packing
reserves another 128 tokens because the two streams pad independently. Use a
derived allocation of at least 128 for full-sequence models and 256 for
DiffusionGemma; increase sequence_len if individual examples do not fit.
SDPA and eager use the full derived capacity.
The default overflow_policy: error raises on an oversized example.
overflow_policy: drop filters and counts oversized examples during
preprocessing; it never truncates them.
Dream can use eos_tail: visible_supervised to extend each logical example to
sequence_len with supervised EOS tokens. These are semantic tokens and
consume packing capacity. Physical padding is excluded from attention and loss.
Packing mainly saves memory; throughput depends on your lengths and attention backend, so benchmark both settings for your workload.
DiffusionGemma attention boundaries
Prompt prefill is causal. During denoising, each token in the current output canvas attends to the clean prefix and every valid token in that same noisy canvas. Bidirectional visibility applies to the output canvas, not the combined prompt and output sequence.
Training uses the same boundary. The encoder can process the complete clean sequence for its auxiliary autoregressive loss, but the decoder mask exposes only keys before the selected output block. Clean copies of that block and later output tokens stay hidden, and causal encoder attention prevents those future tokens from entering the visible prefix states.
Full-attention decoder layers see the entire eligible prefix. Sliding-attention layers see the retained prefix window and the entire current canvas; they do not apply a query-relative sliding window inside the canvas. Both layer types exclude other packed examples. Dream and Nemotron use their full-sequence layouts.
Objective and recurrence options
Unset options inherit the model’s reference behavior. Dream’s CART variant uses
time_weighting: cart and cart_p; its optional focal reweighting uses
token_reweighting: true, alpha and gamma.
DiffusionGemma’s default self-conditioning probability
(self_conditioning.p) is 0.5. Its optional Rhine objectives use
time_weighting: loo or inv_one_minus_t with
objective_reduction: example_mean; rhine_weight_clip caps the
1 / (1 - t) weights. encoder_ar_weight scales the separate encoder loss.
self_conditioning.train_module: true includes the self-conditioning module in
the saved adapter.
diffusion_lm:
unroll:
k_max: 2
grad_through_steps: false
eval_steps: 1With unroll.k_max above 1, each step draws a read count uniformly from 1 to
k_max. Earlier reads run without retained gradients and the final read
supplies the loss. grad_through_steps: true is available only for models with
self-conditioning. eval_steps (1 to k_max) fixes the read count for
evaluation and prediction instead of drawing it; unset, they draw like training.
Adapters, merging and inference
Native training requires LoRA with explicit text attention or dense-MLP
projection targets. DoRA, layer replication, lora_target_linear and
lora_target_parameters are rejected, and native vocabulary resizing is
disabled. Nemotron additionally supports adapter: qlora (4-bit; 8-bit is
rejected), FSDP2 via fsdp_config, and the fused LoRA kernels
(lora_qkv_kernel, lora_o_kernel, lora_mlp_kernel) together with
lora_fp32_gradients; Dream and DiffusionGemma remain unquantized LoRA
without FSDP.
DiffusionGemma’s encoder and decoder text weights are tied, so their LoRA factors are shared. Its targets must be a single expression naming both the encoder language model and the decoder, for example:
lora_target_modules: '^model\.(encoder\.language_model|decoder)\.layers\.\d+\.self_attn\.(q|k|v|o)_proj$'Vision, router and expert parameters stay frozen.
Nemotron supports opt-in Cut Cross Entropy for its aligned diffusion objective and for typed-decision training. With Axolotl’s CCE fork installed, add:
plugins:
- axolotl.integrations.cut_cross_entropy.CutCrossEntropyPlugin
cut_cross_entropy: trueKeep any other plugins the recipe requires in that list. The final training
read computes full-vocabulary CE from hidden states without materializing
vocabulary logits; decision training still computes its restricted-label and
Brier terms over the allowed answers. Targets stay aligned, with no causal
shift. This path requires a plain linear output head and does not support a
LoRA output head or the loo objective.
axolotl merge-lora config.yaml exports a merged checkpoint. When
merge_method is omitted, DiffusionGemma selects legacy so its tied factors
merge once; setting merge_method: memory_efficient for DiffusionGemma raises.
For interactive generation, use axolotl inference config.yaml --chat. Each
reply is a completion block appended to the conversation and denoised in one
piece; see Inference. Native models
generate completions only.
Nemotron Diffusion VLM
nvidia/Nemotron-Labs-Diffusion-VLM-8B (model_type: nemotron_labs_diffusion_vlm)
pairs the Nemotron diffusion backbone with a Pixtral vision tower. Axolotl
resolves it to its own profile: the vision tower and projector are frozen on
load, and 4-bit loading and the QLoRA merge leave the vision stack and
diffusion_head unquantized.
In a lora_target_modules list, the names the Pixtral tower shares with the
decoder (q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj,
down_proj) are anchored to encoder.layers.N.self_attn/mlp, so they cannot
attach to the tower’s projections of the same name. Other entries (for example
diffusion_head) keep PEFT’s suffix matching, peft_layers_to_transform is
folded into the anchored pattern, and projector-only names (linear_1,
linear_2, merging_layer, patch_conv) are rejected. A string (regex)
target is used as written; LoRA that lands on a vision module still fails
after the adapter loads. The profile also patches two
transformers-version mismatches in the published remote code. FSDP2, QLoRA,
varlen attention and the fused LoRA kernels (lora_qkv_kernel, lora_o_kernel,
lora_mlp_kernel) are supported and were measured on text-only batches; image
batches are unmeasured. Under FSDP2 with
auto_wrap_policy: TRANSFORMER_BASED_WRAP, fsdp_config.transformer_layer_cls_to_wrap
defaults to Ministral3DecoderLayer and may name only that class: a
microbatch without images skips the frozen tower, so a vision, projector or
Ministral3RMSNorm unit would all-gather on some ranks only and hang. Text-only
training on this model matches the remote forward exactly in fp32. The profile
sets complementary_mask: false on the loaded config so the remote forward does
not duplicate the batch, and saved or merged config.json files keep that
value; restore the published true before reusing the remote training code.
Its decoder weights are not the LM’s: the VLM checkpoint was trained further,
and its tokenizer carries one extra added token, so step losses differ from
Nemotron-Labs-Diffusion-8B on the same data.
Image inputs are supported only through the typed-decision plugin
(diffusion_decision); plain diffusion_lm SFT on this model is text-only and
rejects multimodal datasets. The model ships no HF processor; the plugin uses
NemotronDiffusionVLMProcessingStrategy from axolotl.processing_strategies,
which ports the remote image math (PNG, JPEG, WEBP, GIF and BMP only; URL
fetches refuse redirects and non-image/* responses and stop at 32 MB or 60 s). An image is
resized so its longest edge is at most max_image_size and each 28x28 block
(patch 14, spatial merge 2) becomes one <|image_pad|> token: a W x H grid
costs W * H pad tokens plus H - 1 row breaks and the start/end markers
(1400 -> 50x50 = 2551 prompt tokens; 784 -> 28x28 = 813). Pixel inputs
reach the forward as pixel_values (N, 3, H, W) padded to the largest image
in the batch and image_sizes (N, 2), in prompt order; the model scatters
them into the pad tokens in sequence order. Image inputs through the
typed-decision plugin are described under Typed-decision training;
examples/nemotron-diffusion/decision-lora-vlm-8b.yaml is the recipe.
Typed-decision training
The diffusion_decision plugin trains a native diffusion model to answer
typed multiple-choice, score and yes/no questions. Each record renders a fixed
decision system prompt from its question schema, sends its state as the user
message, and places one answer label per question in the output canvas using
the canvas template of the public mmastrac/djev project.
examples/nemotron-diffusion/decision-lora-8b.yaml is the starting recipe for
nvidia/Nemotron-Labs-Diffusion-8B. It uses BF16, attn_implementation: varlen,
and rank-64/alpha-128 LoRA over all seven attention and MLP projections with
loraplus_lr_ratio: 8. It trains one epoch with AdamW at 6.5e-6, microbatch
16 and eight accumulation steps (effective batch 128). The decision loss
combines candidate-restricted CE, full-vocabulary CE and a 0.1 Brier term,
with unroll.k_max: 2, no latent slots and non-reentrant gradient
checkpointing. The YAML leaves the model revision unpinned; add
revision_of_model to a local copy for an exact reproduction.
axolotl preprocess examples/nemotron-diffusion/decision-lora-8b.yaml
axolotl train examples/nemotron-diffusion/decision-lora-8b.yamlSet the data_files of the datasets and test_datasets entries to separate
normalized train and development JSONL files. The recipe evaluates every 50
steps and keeps the adapter with the best eval_loss. Keep held-out test data
out of both entries.
Dataset format
type: diffusion_decision.jsonl reads already-normalized JSONL, one record per
line. diffusion_decision.typed_decisions and diffusion_decision.procedural
normalize rows from the typed_decisions and procedural typed-decision source
schemas into the same form.
{"source":"support_demo","family":"returns","id":"case-0042","group":"case-0042","instructions":"Decide from the supplied case only.","state":{"customer":"A customer says their unopened order arrived yesterday and asks for a refund.","policy":"Unopened items delivered within 30 days may be refunded."},"questions":{"refund":{"type":"choice","instructions":"Choose the next action.","options":[{"name":"approve","description":"Approve a refund under the stated policy."},{"name":"deny","description":"Deny the request because the policy does not apply."},{"name":"escalate","description":"Send the case to a human reviewer."}]},"urgency":{"type":"score","instructions":"Rate how urgently this needs review.","levels":["routine","soon","urgent"]},"eligible":{"type":"noul","instructions":"Is the customer eligible for a refund?","criteria":{"true":"The stated policy permits a refund.","false":"The stated policy does not permit a refund."}}},"labels":{"refund":{"kind":"dist","probs":[0.78,0.02,0.20]},"urgency":{"kind":"hard","gold_idx":0},"eligible":{"kind":"set","allowed_set":[0]}},"source_metadata":{"provenance":{"dataset":"my-org/support-decisions","revision":"immutable-revision","split":"train","source_id":"case-0042"}}}source, id and group identify the row; family is optional and is used
for split isolation. Records with the same source, family, state and top-level
instructions that share a group are rendered on one canvas, up to
max_questions_per_canvas questions. There is no per-row system field: put
shared context in state, global instructions in instructions, and
per-question instructions on each question. Keep question IDs stable and
nonempty.
| Type | Required schema field | Canonical alternative order |
|---|---|---|
choice |
options |
List order. An option is a string or an object with name and description; descriptions are rendered. |
score |
levels |
List order, lowest to highest as defined by the source. Do not sort levels lexically. |
noul |
none | yes, then no. Optional criteria documents the true/false conditions and is rendered. |
Label indexes always refer to this canonical order, never to option names or
token IDs. Answer tokens come from labels.codebook: vendored26 uses the
djev letters A-Z (up to 26 alternatives); expanded52 allows up to 52 (A-Z, a-z);
expanded128 adds selected Greek and Cyrillic letters for up to 128. Every label must be one
token in the model’s tokenizer, which the canvas template check enforces at preprocessing.
On nvidia/Nemotron-Labs-Diffusion-VLM-8B, a record may carry a top-level
images list of http(s) URLs, absolute paths, or paths relative to the
directory of the local data file (relative paths are rejected for Hub
sources). Images are opened as stored, without EXIF rotation. The images are
placed before state in the user turn and expanded to their token grids in the
prompt, so they count against sequence_len
(diffusion_decision.max_image_size, default 1400, caps the longest edge; see
Nemotron Diffusion VLM for the token accounting).
Records with different images are never grouped onto one canvas, and
decontamination fingerprints an image record by its state plus the sha256 of
each image file. The prepared-row cache stores resolved references and sizes
only and hashes each image’s bytes, so an edited image or a changed max_image_size
invalidates it. Only the jsonl adapter reads images; URL-referenced images
bypass the cache. Each collator, and each dataloader worker’s copy of it, keeps
up to diffusion_decision.image_cache_size (default 64; 0 disables) normalized
float32 image tensors, up to about 23.5 MB each at 1400 px.
Labels
Every question has exactly one label:
{"kind":"hard","gold_idx":1}
{"kind":"dist","probs":[0.10,0.70,0.20]}
{"kind":"set","allowed_set":[0,2]}hard names one zero-based alternative. set is a nonempty set of unique
valid indexes. dist has one finite, nonnegative probability per alternative
that sums to one within 1e-4; zero entries are valid and must not be omitted.
Partial distributions, ranking targets, per-row candidate token IDs and
hard/soft blending are not accepted.
labels.label_softmax selects candidate-restricted CE (restricted),
full-vocabulary CE over the allowed label tokens (full), or both (the
default). The full-vocabulary term still penalizes probability outside the
valid answers. full_ce_weighting: dft applies DFT weighting to hard-label
full CE. hard_label_smoothing spreads that fraction of a one-hot CE target
uniformly across the question’s valid answer tokens; Brier targets are
unchanged.
Splits and provenance
Keep train, development and test data in separate files or dataset entries,
and use split: train only for training entries. Preserve
source_metadata.provenance with an immutable source location and revision,
the split, and the source row ID. typed_decisions rows train only from a
declared train split and reject a row split that disagrees with it. After
normalization, the loader rejects states or grouped contexts shared between
splits.
Mixture and weighting
mixture.weights sets per-source sampling weights by source. Without
weights, each source is sampled in proportion to its row count raised to
mixture.temperature (default 0.5). max_examples_per_source caps rows per
source. loss_weight multiplies each source’s loss. Loss is averaged over the
questions on a canvas and then over canvases, so a canvas with many questions
does not outweigh a single-question canvas.
Every epoch is drawn afresh with the same number of microbatches as the first:
each capped source contributes its next max_examples_per_source window of
one seeded shuffle (so later epochs see records the first did not, before any
repeat), the mixture draw and the stratified microbatches are redrawn, and
question and option order is seeded by the record, the epoch and how many
times the epoch has already drawn that record. A record’s first draw in epoch 0
uses the order prepared at startup; any other draw builds its canvas when the
batch is loaded by concatenating the record’s token spans pre-tokenized at
startup (prompt preamble, each question’s header and option lines, the format
instruction, the chat turn around them, and each question’s answer piece), so
re-ordering questions and options or reassigning labels needs no tokenizer
(a few milliseconds per canvas, most of it seeding the permutation and
validating the canvas, against about 25 ms to re-tokenize one on a 16k-token
context). This is exact only where no pre-token crosses a span boundary; the
plugin proves that for the Nemotron-Labs-Diffusion tokenizers, checks each
record’s first draw against full tokenization when the spans are built, and
re-tokenizes records or tokenizers it cannot prove. A drawn record that was not
prepared at startup and does not fit the token budget is replaced by a prepared
record of the same source. Epochs are a
function of the seed, the batch settings and the epoch number, so ranks agree
and a resumed run replays the same batches. With training sample_packing
(core multipack) or premixed rows, every epoch reuses the first epoch’s draws.
To train on a prebuilt draw sequence, set mixture.premixed: true and
per_batch_stratified: false. Each training row then needs a unique draw ID
and an origin matching its own identity; copies of one origin must be identical
apart from this metadata:
"source_metadata": {
"premix": {
"draw_id": "run-000001",
"origin": {"source": "support_demo", "id": "case-0042", "group": "case-0042"}
}
}Batching
batch_flattening: true concatenates the logical microbatch and keeps
decisions isolated through the varlen attention path. For variable-length
decisions, set batch_flattening: false, sample_packing: true and
eval_sample_packing: false instead. Packing uses sequence_len as the
per-decision limit and sequence_len * micro_batch_size as the physical token
budget. It requires mixture.per_batch_stratified: false and does not support
sampled latent-slot counts. Reduce micro_batch_size and raise
gradient_accumulation_steps if the effective batch does not fit.
Evaluation batches hold a fixed eval_batch_size rows by default. With
sample_packing: false, set eval_sample_packing: true to pack evaluation
instead: consecutive rows are concatenated, in dataset order, into one varlen
row of at most sequence_len * eval_batch_size tokens, so short rows share a
forward and a micro-batch never exceeds the fixed-batch worst case. This works
with or without batch_flattening for training, applies to full-sequence
layouts (encoder/canvas layouts keep fixed batches), and repeats no row across
ranks. eval_loss and the eval_decision/* metrics remain per-example means,
equal to the fixed-batch values. With sample_packing: true, evaluation is
packed by the same multipack sampler as training unless
eval_sample_packing: false; only one packing mechanism applies.
On a full-sequence layout without free latent slots, training and evaluation
run only the final pass of a k_max unroll: label slots keep their noisy
tokens between passes, nothing self-conditions and no gradient flows through
earlier passes, so those passes would re-read the same canvas and their logits
would be discarded. Loss and gradients are identical to running them, and the
read count is still drawn so the random stream is unchanged. The trainer logs
this once. Earlier passes only feed forward through latent.mode: free
slots, which commit their argmax predictions between passes. The final pass
computes logits only at label slots, so it never materializes
full-vocabulary logits over the sequence.
Prepared-row cache
With dataset_prepared_path set and local JSONL sources, prepared canvases are
cached under <dataset_prepared_path>/diffusion_decision_cache/ and reused when
the cache identity matches: the decision config and the other preparation
fields (seed, sequence_len, model config), the tokenizer files and
vocabulary, the plugin’s code, and the source files. A source file is matched
by its resolved path, size and modification time, so touching it without
changing its content still re-prepares once.
The cache holds the canvas layer only: every decontaminated, grouped training
record per source, with canvases built for the first epoch’s capped subset
(the rest of a capped source is kept compressed for later epochs), the
pre-tokenized canvas spans of every training record, and the evaluation
canvases. Batch settings are not part of its identity:
micro_batch_size, gradient_accumulation_steps, eval_batch_size,
sample_packing, eval_sample_packing, batch_flattening and the mixture’s
weights, temperature and per_batch_stratified only shape the batch plan
(token-budget filter over the cached lengths, mixture draws and microbatch
grouping), which is rebuilt at startup in seconds.
diffusion_decision.prepared_cache_verify: true trades load time for stronger
checks: sources are matched by content sha256 instead, and a cache hit hashes
the stored rows against the hash recorded when they were written. Without it a
damaged or truncated cache file is still rejected when its framing or contents
do not decode, and the rows are prepared again. The two modes key sources
differently, so switching the option prepares and caches once more.
Outputs
Each saved adapter includes diffusion_decision_manifest.json, which records
the base model and revision, diffusion spec, canvas width, label codebook,
latent slots, read protocol and tokenizer special IDs that an external
inference integration must reproduce. When any training or eval canvas carries
images it also records image_inputs: max_image_size and the placement
(images_before_state). Serving must resize with the same limit; the remote
image_processing.py defaults to DEFAULT_MAX_IMAGE_SIZE = 1400, so set it to
the recorded value. Axolotl does not ship a decision-serving
endpoint. Use a separate config for a frozen test split, and do not use test
data for checkpoint selection.
The manifest also records max_questions_per_canvas, canvas_prefix_ids and,
under noise_read_protocol:
read_passes: model forward passes a decision read depends on. Reads mask every label slot in one pass; on a full-sequence layout without free latent slots the later steps of ak_maxunroll re-read the same canvas, so this is1.nullmeans the count is drawn per batch and changes the result.unroll_k_maxandunroll_eval_steps: the configured unroll.generation_diffusion_steps:diffusion_lm.num_diffusion_steps, used only by free-text generation, never by decision training or reads. Older manifests call this fielddiffusion_steps; it is read asgeneration_diffusion_steps.
Loading weights through lora_model_dir, or a merged checkpoint directory
(base_model pointing at a local directory with the manifest), refuses a
config whose codebook, canvas width, canvas prefix, decision layout, questions
per canvas, latent slots, read passes or mask token disagree with the manifest.
Fields an older manifest does not record are not compared. To continue
training an adapter under a deliberately different canvas or codebook, set
diffusion_decision.allow_manifest_mismatch: true; the mismatch is then logged
as a warning. Reads should leave it off.
diffusion_decision.read_overflow_canvas_width is a read-time option for
full-sequence layouts: a read canvas whose answer template does not fit
canvas_width is rebuilt at this wider width instead of being dropped. Canvases
that fit are unchanged, and training rows never widen. The wider canvases are
outside the training distribution, so validate their accuracy before relying on
them.
Public procedural decision mix
scripts/diffusion_lm/build_public_decision_mix.py materializes a reproducible
train/dev mix from
tasksource/procedural-typed-decisions
at revision 916e6cce365a65c37d58db70c9e369795817651e:
python scripts/diffusion_lm/build_public_decision_mix.py ./data/public-procedural
axolotl train examples/nemotron-diffusion/decision-lora-8b-public-procedural.yamlThe output contains only rows from that dataset plus a contract.json
recording the pinned revision and licenses. The dataset card declares
Apache-2.0; the separate generator repository publishes its code under
CC-BY-4.0. Keep contract.json with any materialized output you share. The
model is covered by its own model-card license.
--protected-normalized PATH (repeatable) excludes any public row whose
canonical state matches a row in your normalized evaluation JSONL; that file is
never copied to the output. This is canonical-state decontamination only, not
cross-source group or family disjointness. The train and dev outputs are
checked against each other for state, family and group overlap.
decision-lora-8b-public-procedural.yaml pins the model revision and sets
mixture.temperature: 1.0, which schedules each usable row once. At the pinned
revisions, preprocessing keeps 30,080 train rows at the 2,048-token budget,
which is 235 effective batches of 128, hence max_steps: 235. This mix is a
reproducible starting point for the format, not a quality result.
Public image decision mix
scripts/diffusion_lm/build_public_image_decision_mix.py materializes a
reproducible image typed-decision train/dev/test mix for
nvidia/Nemotron-Labs-Diffusion-VLM-8B:
python scripts/diffusion_lm/build_public_image_decision_mix.py ./data/public-image-decisions
axolotl train examples/nemotron-diffusion/decision-lora-vlm-8b-public-image.yamlTrain and dev rows come from
HuggingFaceM4/A-OKVQA
at revision d1b0efa3a436e9101dfbde3752db7607da696c35 (train and validation
splits) and the image-bearing rows of
derek-thomas/ScienceQA
at revision f18b0a70359ebfb41f658fd564208d0355b013f4 (train and validation
splits). The test file is
nyu-visionx/CV-Bench
at revision bc284db50d036958861cb60cdd7b77612052ce0d (default config, test
split), a held-out benchmark that is never used for training or checkpoint
selection.
Each row is one choice question named answer with the source’s answer
options in their original order and a hard label. state.question holds the
question text; ScienceQA rows also carry state.context from the source hint.
family is the A-OKVQA question_id, the ScienceQA row id (its subject and
topic stay in source_metadata.attributes), or the CV-Bench task. Every row has images: ["images/<sha256>.<png|jpg>"], a path
relative to the JSONL file. Image bytes are written once per content hash;
PNG and JPEG sources keep their original encoding and anything else is
re-encoded as PNG. Rows without an image, with duplicate or empty options, or
with an answer outside the option list are dropped.
--train-per-source (default 4096), --dev-per-source (default 256) and
--test-cap (default 0) set the rows taken from each source. Each nonzero
value is an exact per-source count, and the build fails when a source has fewer
usable rows; 0 keeps every usable row for all three flags; negative values are
rejected.
Selection sorts rows by sha256(seed\0source\0split\0id), so the same caps
always yield the same rows. CV-Bench states and images are excluded from train
and dev, train states and images are excluded from dev (images by sha256 of
their bytes; a state is its fingerprint plus its image digests, as in the
plugin’s decontamination, so a templated question over a different image is
kept), and source/group pairs, families and image digests are disjoint
across splits, so the plugin’s family decontamination keeps every train row. contract.json records the pinned revisions, license evidence, per-split
counts and drop reasons (no_image and invalid-option drops count the whole
split; state_overlap and image_overlap count only the rank-ordered rows read
until the count filled, as recorded under scanned), the sha256 of each JSONL file, and the image count and
total bytes.
Licenses: CV-Bench declares Apache-2.0, but its images are repurposed from
COCO, ADE20K and Omni3D and keep those sources’ terms. The ScienceQA card
metadata says cc-by-sa-4.0 while its Licensing Information section links
CC BY-NC-SA 4.0; treat the NonCommercial reading as binding until the card is
resolved. Its images were collected with the questions from K-12 science
curricula, and the card states no image-specific licence. The A-OKVQA card
declares no license; the A-OKVQA project repository is Apache-2.0. Its images
are COCO 2017 Flickr photos under the Flickr Terms of Use with per-image
licences; COCO’s CC BY 4.0 covers the annotations, not the images. These flags
are repeated in contract.json["license_flags"]. Keep contract.json and the images/
directory with any materialized output you share.
decision-lora-vlm-8b-public-image.yaml sets diffusion_decision.max_image_size: 784, so each image costs at most 813 prompt tokens (28 x 28 = 784 pads, 27 row
breaks, start and end) inside the 2,048-token budget, and mixture.temperature: 1.0, which schedules each usable
row once: the 8,192 train rows all survive decontamination, which is 64
effective batches of 128, hence max_steps: 64. Lower max_steps if
preprocessing drops rows at the budget. It evaluates and saves every 32 steps;
keep eval_steps and save_steps dividing max_steps so
load_best_model_at_end also scores the final step.
This mix is a reproducible starting point for the image record format, not a
quality result.
Converting a causal LM
diffusion_lm.from_causal_lm: true trains an ordinary causal checkpoint with
the legacy masked-diffusion objective, controlled by noise_schedule,
min_mask_ratio, max_mask_ratio, importance_weighting and the mask-token
options. It requires plugins: [axolotl.integrations.diffusion.DiffusionPlugin],
which supplies the diffusion trainer; without it the config is rejected. Existing
configurations with a deprecated diffusion: block still load through the same
plugin, which translates them to this form.