INDIE / MACHINE
BACK TO ARCHIVE
FIG. 04YUMON SERIES2026-09-10

Yumon Pet: The Lowest-Loss Checkpoint Wasn't the Best One

BUILD SPEC
UNCHANGED
  • [object Object]
  • [object Object]
  • wgpu = "26.0.1" (pulled in by cubecl)
EDITION
2024
OS
Windows 11 Pro (64-bit), build 10.0.26200
BACKEND
burn Wgpu backend, default instance/backend selection (not explicitly pinned to Vulkan/DX12)

Yumon Pet trains a small decoder-only transformer from scratch in Rust, using burn for the model and cubecl's Wgpu runtime for the flash-attention kernel. The repo has been through a dozen training runs at different capacities, sequence lengths, and data mixes, and every run leaves behind a metadata.json with a final training loss. The obvious move is to pick the checkpoint with the lowest number and call it the best model. I ran three of those checkpoints - small, medium, large, same data recipe, same prompt - to check whether that's actually true.

It isn't. The checkpoint with the lowest loss in the entire checkpoints/ tree produced the worst prose of the three.

The three checkpoints

All three are DecoderOnly architecture (src/brain/decoder_model.rs, single causal stack, prompt and reply concatenated into one sequence - as opposed to the older EncoderDecoder architecture in src/brain/model.rs, cross-attention over a separate encoder stack), trained on the Structured stage, where the model learns to emit JSON ({"action": ..., "emotion": ..., "reply": ...}) rather than free text.

Checkpointembed/hiddenlayersheadsff_dimmax_seq_lenbatch_sizeepochs trainedfinal lossmodel.bin
brain-all-dec-only/128h_2l_2a_220len_6e128225122203240.5786.02 MiB
brain/256h_3l_4a_512len_b2_DecoderOnly_Structured256341024512230.62220.02 MiB
brain/512h_6l_4a_512len_b2_DecoderOnly_Structured512642048512210.165112.05 MiB

That last row is the one to notice before reading any generated text: it has less than half the loss of the other two, and it got there in a single epoch. The run config for it in src/brain/train.rs asks for 15 epochs at that batch size; it stopped at 1.

Getting real output out of them

cargo run -- chat in src/main.rs looks like the headless inference path - it loads the vision model, loads the brain checkpoint, prints the detected emote - and then does nothing with the model. The actual generation call is commented out:

// src/main.rs, run_chat()
// let result = brain_model.generate(
//     &tokenizer,
//     &class_probs,
//     &emote_probs,
//     user_emote_idx,
//     prompt,
//     80,   // max tokens
//     &device,
// );
 
// println!("\n┌─ Yumon says ──────────────────────────────────────────┐");
// println!("│ {}", result.reply);
// println!("└───────────────────────────────────────────────────────┘");
 
Ok(())

The real generation path lives in chat_ui.rs, but that binary pulls in the desktop feature (tao, wry, three-d) for its terminal UI shell, which is unrelated to running a model. So there's no way to get a checkpoint's actual output from the command line without either building the full desktop stack or writing a thin wrapper. I added src/bin/headless_compare.rs - no GUI deps, just YumonDecBrain::load + generate_unmasked_parsed, with the same prompt-wrapping chat_ui.rs uses for the Structured stage:

// src/bin/headless_compare.rs
fn wrap_prompt(stage: TrainingStage, prompt: &str) -> String {
    if stage == TrainingStage::Structured {
        serde_json::to_string_pretty(&serde_json::json!({
            "memories": Vec::<serde_json::Value>::new(),
            "message": prompt,
        }))
        .unwrap()
    } else {
        prompt.to_string()
    }
}
 
let (model, tokenizer, config) = YumonDecBrain::<Wgpu>::load(checkpoint, &device)?;
let wrapped = wrap_prompt(config.training_stage, prompt);
let result = model.generate_unmasked_parsed::<WgpuRuntime>(
    &tokenizer,
    &wrapped,
    config.max_seq_len,
    &device,
);

Built headless with cargo build --no-default-features --bin headless_compare - --no-default-features drops the desktop feature, so no GTK/WebKit/udev equivalents are needed on this platform either.

Evidence

Same prompt against all three, this session, this machine:

cargo run --no-default-features --bin headless_compare -- dec <checkpoint> "how are you feeling today"

Small (128h/2l/2a, loss 0.578):

{
 focusedec"
}
 focused estate
{
 mix" attack":ec" portfol",
 mix" ingace":ec" entrepreneurs vide",
 mix" happeningment":ec" peiter shly br studentated0"
}

Not valid JSON. No reply field the schema parser could even extract - result.reply came back empty.

Medium (256h/3l/4a, loss 0.622):

{
  " action": " get help",
  " emotion": " happy",
  " reply": " you're looking for the following command in this case, and it is in the context of the text."
}

Valid schema, grammatical, but non-responsive - "how are you feeling today" gets an answer that reads like tech-support boilerplate.

Large (512h/6l/4a, loss 0.165 - the lowest of any checkpoint in the repo):

{
   " action": " follow",
  " emotion": " fearful",
  " reply": " that can use and the company's better to work in the future-s of their own ticers as a single."
}

Valid schema, but worse grammar than the medium checkpoint - "future-s," "ticers" aren't words, and the sentence doesn't parse. This is the checkpoint with the best loss number in the whole repo.

Test hardware, this machine, this session:

GPUIntel(R) UHD Graphics 770 (integrated, driver 32.0.101.7085)
CPU12th Gen Intel(R) Core(TM) i5-12500, 6 cores / 12 threads
RAM32 GB
OSWindows 11 Pro (64-bit), build 10.0.26200

Loss-to-quality ranking across the three: by loss, large (0.165) < small (0.578) < medium (0.622). By actual output quality: medium > large > small. The two orderings don't agree at any position. Loss here is a training-stability signal (did the schema's fixed punctuation and keys get learned) more than a content-quality signal, and it's not comparable across runs with different batch sizes and wildly different epoch counts without controlling for those first - which none of these three do.

Decision log

Failure notes

What's next

None of these three checkpoints is what Yumon Pet should ship with - that's the point of running them side by side rather than picking one. The large checkpoint is one epoch into a fifteen-epoch plan; finishing that run, or the medium one, with loss tracked but not treated as the finish line, is the next concrete step before there's a checkpoint worth writing a "here's what Yumon says" post about.

PREV
Product Hunt Pick: OpenObserve's AI Observability, for Debugging Agents That Cross Every Layer of Your Stack
NEXT
Hot Reload for Entropy's TS Addon Engine, Without Resetting GPU State
INDIE MACHINE© 2026
A RUST BUILD LOG. NO MOCKUPS, NO ASSUMED NUMBERS.