training: fuse LoRA onto 16-bit base + disable Xet to fix GGUF export #27

Manually merged
mdoorn merged 4 commits from feat/027-training-fuse-lora-onto-16-bit-base-disable-xe into master 2026-02-03 16:26:23 +01:00
Owner

training: fuse LoRA onto 16-bit base + disable Xet to fix GGUF export

Along the way:

  • training: drive GGUF conversion with stock llama.cpp, not Unsloth's converter
  • corpus: unify diagnostic surface, kill OPEN-1, onboard greeter reconciled
  • corpus: move invalid-actor-body out of the corpus into a resolve fixture

Touches 27 files: corpus_diagnostics.md, docs/effects.md, scripts/test.sh, scripts/training/python/eval.py, scripts/training/python/export_gguf.py, scripts/training/python/train.py and 21 more.

training: fuse LoRA onto 16-bit base + disable Xet to fix GGUF export Along the way: - training: drive GGUF conversion with stock llama.cpp, not Unsloth's converter - corpus: unify diagnostic surface, kill OPEN-1, onboard greeter reconciled - corpus: move invalid-actor-body out of the corpus into a resolve fixture Touches 27 files: `corpus_diagnostics.md`, `docs/effects.md`, `scripts/test.sh`, `scripts/training/python/eval.py`, `scripts/training/python/export_gguf.py`, `scripts/training/python/train.py` and 21 more.
mdoorn approved these changes 2026-02-03 11:45:49 +01:00
mdoorn left a comment

Approving. I would have split src/rust/crates/ck_driver/tests/corpus.rs differently, but not enough to hold this up.

Approving. I would have split `src/rust/crates/ck_driver/tests/corpus.rs` differently, but not enough to hold this up.
@ -409,3 +403,1 @@
col,
}
})
.map(|diag| diag_to_trap(diag, &test.pages[diag.span.file.0 as usize].source))
Owner

The as cast truncates silently if map is ever wider than the target. Is the bound guaranteed, or should this be try_from?

The `as` cast truncates silently if `map` is ever wider than the target. Is the bound guaranteed, or should this be `try_from`?
@ -120,3 +374,1 @@
// the body's effect calls / asserts.
let events = run_corpus_main("helloWorld");
assert_clean_lifecycle(&events, "Main");
fn corpus_apps_match_their_trap_md() {
Owner

corpus_apps_match_their_trap_md reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?

`corpus_apps_match_their_trap_md` reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?
Author
Owner

Split. The decision in corpus_apps_match_their_trap_md is a pure function now, with its own tests in src/rust/crates/ck_driver/tests/corpus.rs.

Split. The decision in `corpus_apps_match_their_trap_md` is a pure function now, with its own tests in `src/rust/crates/ck_driver/tests/corpus.rs`.
Author
Owner

Not guaranteed. map goes through try_from now and the overflow is a typed error in src/rust/crates/ck_cktest/src/lib.rs.

Not guaranteed. `map` goes through try_from now and the overflow is a typed error in `src/rust/crates/ck_cktest/src/lib.rs`.
hkowalski approved these changes 2026-02-03 15:00:04 +01:00
hkowalski left a comment

Looked through src/rust/crates/ck_driver/tests/corpus.rs as well. Fine by me.

Looked through `src/rust/crates/ck_driver/tests/corpus.rs` as well. Fine by me.
The step-4 GGUF export crashed with a missing config.json: the adapter
is QLoRA-trained on the bnb-4bit base, and Unsloth's merge_and_overwrite_lora
refuses a 16-bit merge from an nf4 base (saving_utils.py), silently leaving
merged/ empty. Re-point the resolved base at the ungated 16-bit repo
(unsloth/gemma-4-E4B-it) so the merge downloads + fuses onto clean weights.

The 16-bit base download then hung at 4% behind the VPN: HF's Xet backend
opens ~96 parallel CAS connections that degrade ("connection struggling")
and stall with no timeout. Disable Xet (HF_HUB_DISABLE_XET=1) before any HF
import in all three download entrypoints so every invocation path uses the
robust single-stream CDN download; setdefault keeps =0 override working.
After the merge succeeded, save_pretrained_gguf still crashed: Unsloth drops a
newer unsloth_convert_hf_to_gguf.py (passes fp8_as_q8) onto an older llama.cpp
clone whose ModelBase.__init__ rejects that kwarg -> TypeError, swallowed by
capture_output. The clone's own stock convert_hf_to_gguf.py is self-consistent.

Rewrite export_gguf.py to: save_pretrained_merged (16-bit) -> resolve the stock
converter + llama-quantize via unsloth_zoo.check_llama_cpp (bootstrapping the
clone with install_llama_cpp on fresh machines) -> bf16 GGUF -> Q4_K_M, with
live subprocess output (no swallowed errors) and the 15GB bf16 intermediate
deleted by default (--keep-bf16 to retain).

Verified end-to-end: merge -> GGUF -> `ollama create cklang-gemma:dev` (5.3GB)
-> generation. ~1m37s after the base is cached.
Land the corpus-harness migration and resolve the statement-separation
triage that was blocking greeter.

Harness (ck_driver/tests/corpus.rs + ck_cktest):
- file-role classifier skips config/.env/manifest from the module surface
- per-file `### <File>` trap.md blocks, reconciled strict-exhaustive both ways
- COVERAGE manifest replaces the per-app sweep; every folder named, none silent
- inspect mode (corpus_inspect) added; parser-only smokes
  (ck_parser/tests/corpus.rs, corpus_stdlib.rs) retired — subsumed

OPEN-1 (statement separation) was never a real grammar question:
docs/syntax.md already locks `;`-termination with newlines as trivia. The
greeter test fn body and the docs/effects.md `say_hello` example it was
copied from were missing-`;` defects, now fixed. greeter onboards
reconciled with a trap.md for its two stdlib-unresolved E0301s (Log,
LogLevel) — the same gap helloWorld traps. counter stays parked for its
unparsed actor-messaging surface.

Gates: cargo test -p ck_driver -p ck_cktest green (corpus reconciles both
ways), clippy --all-targets -D warnings clean.
The corpus is positive examples only — real, runnable CkLang that shows how
an implementation is written. A deliberately-broken program is a unit test,
not a real-world example, so it has no place there.

- delete tests/corpus/invalid-actor-body/ (spawn body calling an undeclared
  name); drop its negative #[test], COVERAGE entry, and main-file list item
- add tests/resolve/actor_body_unknown_reference.cktest asserting E0301 on the
  undeclared name, with a `# trap` block so it stays 0-untrapped (conformance
  ratchet holds at 112)
- record the principle at the top of tests/corpus/README.md: corpus = positive
  examples; unhappy paths live in .cktest fixtures; corpus trap.md gaps are the
  unfinished compiler's failures on valid code, never authored-invalid source

Gates: conformance 2/2 (ratchet 112), corpus 10/10, clippy -D warnings clean.
mdoorn manually merged commit ee6d24114a into master 2026-02-03 16:26:23 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
northgale/cklang-bootstrap!27
No description provided.