fix(train): gfx1201 ROCm fixes + unsloth bnb-4bit Gemma 4 31B QLoRA#101
fix(train): gfx1201 ROCm fixes + unsloth bnb-4bit Gemma 4 31B QLoRA#101ulises-c wants to merge 88 commits into
Conversation
- Patch caching_allocator_warmup on HIP/ROCm to silence "Found no NVIDIA
driver" raised by the CUDA-only allocator warmup in recent transformers
- Force device_map={"": 0} for live QLoRA quantization so bitsandbytes can
quantize all layers on GPU (auto can dispatch to CPU, which bnb rejects)
- Add Stage 2 SFT config for Gemma 4 26B-A4B-IT on R9700 (gfx1201, NF4)
Closes #100
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Pivot the gfx1201 SFT path to the community pre-quantized checkpoint unsloth/gemma-4-31B-it-unsloth-bnb-4bit (issue #100): it downloads already-NF4 (~19 GB), skipping the ~62 GB BF16 CPU staging at load that was the R9700's actual blocker — no L40S prequant + rsync needed. - Add `make train-gemma4-31b-stage2-unsloth` (TRAIN_PREQ=true + unsloth base) - Extend device_map={"":0} to the whole QLoRA path: the unsloth checkpoint is dynamic 4-bit (embeddings/lm_head/gates stay 16-bit) and "auto" could dispatch one of those modules to CPU, which bitsandbytes rejects - Document the prequant vs live-quant launch paths in the 31B config - Drop the 26B-A4B config (wrong target; 31B is the real one) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gfx1201 learnings from PR #99 — relevant to this PRPicking up Gemma 4 31B QLoRA on the R9700 after the Qwen3.5 classifier work in #99. Key findings that apply here: 1. hipBLASLt may be safe for Gemma 4 — worth testing firstThe GPU page faults (rc=134) in #99 were specific to 2. Add
|
|
Notes from #94 # From 5090 Run
Critical finding - the SFT model is generatively broken.
The model generated 2048 tokens in 38s (~53 tok/s, fast) but output is textbook repetition collapse:
这样可以帮助他建立更强的数学基础。这样可以帮助他建立更强的数学基础。
这样可以帮助他建立更强的数学基础。[~70+ repetitions, never emits EOS]
Going back to the morning's smoke test, it ALSO showed early degeneration patterns - the trailing `*(此处是观察点....)* *(此处是引导点....)* *(此处是总结点....)*` was the same loop just truncated at 200 tokens. I missed it.
Diagnosis: the model never emits EOS. Without a max_tokens cap, every teacher call runs to context Limit (~150K tokens, taking ~45 min each). The 88x stowdown I saw wasn't cold-start overhead - it was the model never finishing a single response.
What this means for the eval
We sit somewhere in the STATUS_REPORT §2.8 outcome matrix's "fine-tuned Gemma loses both" cell:
> Either undertraining or data-format issue / Document honestly in §Limitations; Locked headline stands at 72.24
Two paths forward
Path A - Run the eval with the cap, report the (likely bad) numbers as evidence of the failure mode. Negative result is still a paper contribution: documents that Pattern-A SFT on this size dataset caused output collapse. Locked headline 72.24 stays as A-grade submission.
Path B - Diagnose root cause first. Most likely candidates:
1. Pattern-A format trained the model to depend on specific structural markers - without them at inference,
2. 05_K_M guantization damaged a small model - test by re-running smoke on the BF16 merged checkpoint (but won't fit on 5090 VRAM; CPU inference is very staw)
3. Chat template mismatch-training used a patched template; serving uses llama.cpp's llama.cpp's Possible structural mismatch.
Which way do you want to play it? My recommendation is Path A first (~45 min to get hard numbers showing the failure) then Path B (post-mortem after we have the data). The negative-result narrative is paper-worthy and matches what §2.8's outcome matrix already anticipates. |
Session update — schema drift fix + training pausedRoot cause (from 5090 run)The 5090-trained adapter is generatively broken: never emits EOS, runs to context limit (~150K tokens, ~45 min/call). Smoke test and full eval showed the same repetition collapse. Root cause diagnosed via A/B (same model, same GGUF, same server):
The failure is a train/serve schema mismatch, not quant damage or chat-template corruption:
Fix: new
|
… fix Training data (dataset.py): - Add socrat-zh-sft / socrat-en-sft sources: one (system, user, asst) triple per dialogue turn using the exact message format socrates_teacher() sends at inference (历史对话记录 blob + 当前学生输入 label + 7-line rules system prompt). Fixes the train/serve schema drift that caused output collapse on the 5090 run (repetition loop, never emits EOS, ~45 min/call to context limit). - Split at dialogue level before per-turn expansion so all turns of a dialogue stay in the same train/test partition — prevents history blobs in test records from containing teacher responses seen during training. - Render-diff verified MATCH: True (system and user byte-identical to inference). - Existing socrat-zh / socrat-en multi-turn loaders untouched (backward compat). train_sft.py: - Only pass quantization_config to from_pretrained when not None. Explicit None caused auto_factory to overwrite the unsloth checkpoint's config.json quantization_config via from_dict(**unused_kwargs), crashing supports_quant_method. - W&B: auto-detect via _check_wandb() (wandb.login relogin=False); warn and disable if not authenticated. report_to=["wandb"] wired into SFTConfig with run_name defaulting to output-dir basename. pyproject.toml: wandb>=0.18.0 added as core dependency. .gitignore: wandb/ local run artifacts added. TRAIN_SOURCES updated to socrat-zh-sft,socrat-en-sft in stage2 config. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code review patch — dialogue-level split fix (d04bc83)Finding: the initial Fix: split at the dialogue level first, then expand to per-turn records. All turns of a dialogue now stay in the same partition, matching the behaviour of the original multi-turn loaders. # before — splits flat per-turn list
converted = [turn for r in raw for turn in _socrat_zh_to_sft_records(r)]
return _split(converted, split, seed) # ← scatters a dialogue's turns
# after — splits dialogues, then expands
split_raw = _split(raw, split, seed) # ← whole dialogue goes to one split
return [turn for r in split_raw for turn in _socrat_zh_to_sft_records(r)]Record counts unchanged: 77,202 train / 8,578 eval. All 143 tests pass. Pushed as d04bc83. Other review findings (accepted / deferred):
|
Bilingual (EN/ZH) teacher scaffolding — pros/cons of building it outConsidered making the SFT format language-aware (English scaffolding for What it concretely requiresIt's bigger than the teacher prompt alone, because several pieces are Chinese-only:
Pros
Cons / risks
Recommendation: phase itShip the Chinese-only fix first (it's the real #94 fix), launch the zh run, and treat bilingual as a deliberate follow-up — and when doing it, do the consultant too, or English parity is only half-built. Full detail, design, and acceptance criteria in #108.
|
Add tests/test_sft_inference_format.py: unit tests for the per-turn socrat-zh-sft / socrat-en-sft sources plus a render-diff gate that drives the real socrates_teacher() path (intercepting call_teacher_wrapped) and asserts the SFT record renders identically under the chat template's per-message trim. Pins the train/serve schema-drift fix (#94, #101) so future drift breaks CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-no-sync
This venv has the packages importable but not their console-script entry
points, so `uv run --no-sync pyright|codespell|pytest` failed to spawn
("No such file or directory"). Switch the type-check, spell-check, and test
steps to module invocations (`python -m pyright` / `codespell_lib` / `pytest`),
which resolve without the console scripts and without forcing a sync. Run
`make install-hooks` to refresh .git/hooks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…te runbook) Captures current state (structural drift fixed in d04bc83; test gate in e5a8a7d), the two remaining residuals to fix on this dev box (action line via get_action_for_state, eval line via the dataset's free-form evaluation field), the test-gate updates, and the R9700 runbook (faithful free-form-eval EOS gate before the full run). Bilingual (#108) explicitly deferred. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The committed socrat-zh-sft / socrat-en-sft loaders still drifted from the real socrates_teacher() inference prompt on two user-message lines, the last residuals of the #94 train/serve schema mismatch that collapsed the first Stage-2b model into a non-terminating repetition loop: - action line: emitted the raw dataset turn["action"]; inference sends get_action_for_state(state). Systematic mismatch (e.g. a1: "生成一个问题" vs "生成一个与解题相关的子问题") on every opening teaching turn. - eval line: emitted templated "学生处于 {state} 状态"; the dataset already carries a per-turn free-form `evaluation` field, far closer to the live consultant prose inference actually sends. Changes: - socratic_teaching_system.py: hoist state_to_action to a module-level STATE_TO_ACTION + get_action_for_state() so the loader shares the exact inference source of truth (behavior-preserving; AST-verified identical to HEAD; live socrates_teacher render byte-for-byte unchanged — no eval re-baseline). - dataset.py: _build_inference_user_message now takes `evaluation`; both -sft builders use get_action_for_state(state) for the action line and the dataset `evaluation` field for the eval line (fallback to the old template only if absent). Target-turn gating still keyed on dataset state+action so the record set is unchanged. - test_sft_inference_format.py: un-rig the action dimension (parity test now asserts the action line render-equal too) and isolate the eval line as the sole, expected residual under a free-form consultant. Verified on this dev box: full suite 154 passed / 2 skipped; the real socrat-zh-sft / socrat-en-sft sets (42,890 records each) carry 0% template- fallback evals; official dry-run plumbing passes. Refs #101, #94. Bilingual EN/ZH (#108) deferred — EN stays Chinese-scaffolded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 0 applied — Stage-2b SFT is now dev-box "100% ready" (
|
| Line | Was emitting | Inference actually sends | Fix |
|---|---|---|---|
苏格拉底教学顾问建议的操作: |
raw dataset turn["action"] |
get_action_for_state(state) |
loader now calls the same canonical map — byte-exact |
苏格拉底教学顾问评估结果: |
templated 学生处于 {state} 状态 |
live consultant prose | loader now uses the dataset's per-turn free-form evaluation field |
The action drift was systematic — a1 (every dialogue's opening teaching turn) never matched (生成一个问题 vs 生成一个与解题相关的子问题), plus several c-states.
Changes
socratic_teaching_system.py— hoistedstate_to_actionto a module-levelSTATE_TO_ACTION+get_action_for_state()so the loader imports the exact inference source of truth. Behavior-preserving: AST-verified all 35 entries identical to HEAD, and the livesocrates_teacherrender is byte-for-byte unchanged for a known turn (no eval re-baseline — headline 72.24 / Tables 6/14 untouched).dataset.py—_build_inference_user_messagetakesevaluation; both-sftbuilders source the action fromget_action_for_state(state)and the eval from the datasetevaluationfield. Target-turn gating still keys on datasetstate+actionpresence, so the record set is unchanged.test_sft_inference_format.py— un-rigged the action dimension (the parity test previously passed the dataset action straight into inference, so it never tested the action line). The render-diff gate now asserts the action line render-equal too; the eval line is isolated as the sole expected residual.
Verification (this dev box)
- ✅ Full suite 154 passed / 2 skipped; ruff / pyright / codespell / shellcheck clean (pre-commit gate).
- ✅ Loaded the real HF sources —
socrat-zh-sftandsocrat-en-sft, 42,890 records each — and confirmed 0% template-fallback evals: every record carries the dataset's free-formevaluation, so the eval-line fix is not a silent no-op. - ✅ Official
make train-gemma4-31b-dry-runplumbing passes. - ✅
zhinference prompt confirmed identical to pre-change HEAD.
⚠️ The EOS gate on the R9700 is still MANDATORY — and must use a live free-form eval
Phase 0 closes the structural and action-line drift, but the eval line still drifts at real serve time — the consultant regenerates fresh prose on every call, so it will not match the dataset's stored annotation token-for-token. test_evaluation_line_is_the_only_residual_drift_under_freeform_consultant pins this as the irreducible residual. Do not read "parity test passes" as "model is collapse-proof." Before trusting the multi-hour run, train a short checkpoint and prove EOS termination on a prompt built with a realistic multi-sentence Chinese evaluation, end-to-end through process_student_input (gold) or via the _capture_inference_prompt path (lighter). A gate built on the 学生处于 {state} template is blind to exactly this residual and will pass while shipping a broken model.
Carry-forward notes for the R9700 launch (§5)
- Recompute run size — the config comment ("~2298 steps / 11 GB") is stale (per-dialogue era). Per-turn is ~77k train records → ~14k steps at
bs1×ga16×3ep(multi-day). WithTRAIN_SAVE_STEPS=100that's 140+ adapter checkpoints (~70 GB) — raiseTRAIN_SAVE_STEPSand/or setsave_total_limitbefore launch. - GPU must be clean —
rocm-smi --showpidsempty (a prior rc=134 leaves the KFD dirty). - hipBLASLt off by default (safe);
=1is an optional ~4× perf bet — test 20+ steps first (feat(train): Qwen3.5-0.8B LoRA classifier — W&B tracking + transformers 5.9 drift fix #99).
Out of scope / known gaps (deferred, not bugs)
- Bilingual (EN/ZH) Socratic teacher: language-aware train + serve scaffolding #108 (bilingual EN/ZH) —
socrat-en-sftstays Chinese-scaffolded: its Englishevaluationprose is dropped into the Chinese teacher prompt as-is. Intended for now. - Parity-test blind spot (pre-existing, not introduced here): the action line is now structurally tautological (both train + serve route through
get_action_for_state, so map coverage cannot break parity — good). But that means a future edit corrupting a non-a1map entry (e.g.b2) would corrupt both sides identically and pass every test. Onlya1is pinned by an independent literal assertion; the 35-entry AST-vs-HEAD purity check was point-in-time, not CI. Flagging as a known gap, not fixing in this PR.
gfx1201 ROCm training — issues encountered on feat/gfx1201-rdna4-qlora-fla-trainingAttempting to reproduce the EOS-gate checkpoint run on the R9700 (gfx1201, 32 GB). Documenting what went wrong and what fixed it. Issue 1 —
|
Picks up PR #99 (Qwen3.5 training, flash-linear-attention CUDA guard, AMD/NVIDIA split GPU smoke tests). .env.example conflict resolved by merging both WANDB comment blocks; uv.lock takes main's version. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove expandable_segments:True from all PYTORCH_HIP_ALLOC_CONF entries; HIP allocator does not support it and the warning preceded GPU page faults at step 16 on gfx1201 - Switch eos-gate target to TORCH_USE_HIPBLASLT=1 — Gemma 4 uses standard softmax attention (no linear-attn delta-rule kernel), so hipBLASLt is safe and avoids the rocBLAS fallback overhead - Replace nohup with setsid on eos-gate launch so the whole process tree is in its own group and can be killed cleanly without leaving GPU dirty - Add docs/HANDOFF_SFT_SCHEMA_DRIFT_FIX.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consolidated, externally-shareable writeup of the gfx1201 training findings from #100/#79/#101 — framed against the NVIDIA/CUDA baseline, with a confidence taxonomy (confirmed/untried/upstream/correction). Companion to the serving-focused GPU_SUPPORT.md. Tracks #109. Also allow "hsa" in codespell (HSA_OVERRIDE_GFX_VERSION env var). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the ulises-c/csen-346 repo link at the top and in references, and make GPU_SUPPORT.md links absolute so they resolve in the public gist (relative links break in the flat gist copy). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Commit 860fc5f (this branch, 2026-05-30) stripped expandable_segments:True from all PYTORCH_HIP_ALLOC_CONF entries after its "unsupported" warning preceded GPU page faults at step 16 — reversing the PR #79 "works and helps" finding the report had presented as settled. Reframe as contested with the full three-flip timeline; latest empirical verdict is OFF. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
wandb>=0.18.0 was promoted to a core dep in pyproject.toml but uv.lock still listed it only under the wandb extra, failing CI's `uv lock --check`. Regenerated; no version changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ontinuity The monitor's WANDB_PROJECT patch (aebcc3f) lives in monitor_stage2.sh, which is already parsed into the running monitor's memory — so it does NOT apply to the current crawl's next relaunch (only to a monitor restart = a fresh run). train_sft re-reads this config on every `make` relaunch, so putting WANDB_PROJECT here means the NEXT CRASH picks it up without restarting the monitor. Also pins WANDB_RUN_ID + WANDB_RESUME=allow so every crawl resume appends to ONE continuous run instead of spawning a new W&B run per crash cycle (the reason the step axis looked empty — fragmented short runs). Requires W&B auth; if login fails train_sft._check_wandb still disables reporting regardless of these vars. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The crash PR comments had an empty ``` block because crash_hint grepped for "page fault|traceback|runtimeerror|out of memory|killed", but the gfx1201 fault prints "Memory access fault by GPU node-1 ... Page not present" and aborts with "Aborted (core dumped)" — none of which match (it is "Page not present", not "page fault"), and a hard HSA abort emits no Python traceback. Broaden the pattern to the real ROCm/HSA signature (memory access fault, page not present, hsa_status, aborted, core dumped, hip/cuda error) and lead with the last tqdm step, so each crash post shows the fault step — the N we are measuring — directly in the PR instead of forcing a dig into crashlogs/. NB: crash_hint runs inside the long-lived monitor process, so this takes effect on the next monitor (re)start, not the current crawl's next relaunch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Issue posted in rocm-libraries #7992 |
Adds two scripts to produce the standalone reproducer needed for the upstream ROCm/rocm-libraries report (#113 comment 1): - capture-rocblas-bench.sh: wraps a one-shot training resume with ROCBLAS_LAYER=2 + ROCBLAS_LOG_BENCH_PATH, verifies the env vars work first, then extracts the exact rocblas-bench invocation at M=608 N=5376 K=21504 from the live Tensile dispatch - replay-rocblas-bench.sh: replays the captured line under AMD_LOG_LEVEL=3 and checks for the GPU page fault + MT64x64x64/ISA1201/DTVB1 ShaderName Both scripts must be run after the production crawl (monitor_stage2.sh) releases the GPU — not while it is running. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t before restart Crawl reached checkpoint-1230/4826 (~25.5%) before a tokenization deadlock left a wedged 24 GB KFD context. All 8 retries failed preflight. Documents GPU reset sequence and tokenization deadlock workaround for next session. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds HFCheckpointCallback to train_sft.py — pushes checkpoint-N to a HuggingFace model repo in a background thread after every TRAIN_HF_PUSH_EVERY steps (default 50) and unconditionally on train end. Commit message encodes step, epoch, loss, and token accuracy. Skips gracefully if the previous push is still in-flight. Opt-in via TRAIN_HF_REPO env var; wired into the train-gemma4-31b-stage2-unsloth Makefile target pointing at ulises-c/SocratesLM-31B-stage2b-QLoRA. Also bumps save_total_limit 2 → 5 so the local crawl retains enough history to recover from multi-step crashes without touching HF. Includes gfx1201 env diagnostics report from 2026-06-05 capture run. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move HFCheckpointCallback out of main() into src/project/hf_callback.py so it is importable and testable. Removes the nested import threading that required a ruff --fix workaround on every commit. Adds 7 unit tests covering step gating, skip-if-in-flight, missing checkpoint dir, commit message format, forced on_train_end push, and thread join. All 162 tests pass. Also updates the session 6 handoff. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Minimal Python script that constructs a bnb.nn.Linear4bit layer at the exact fault shape (in=21504, out=5376, bias=True, nf4, bf16) and runs 200 forward passes under AMD_SERIALIZE_KERNEL=3 to dispatch the buggy Tensile ISA1201 DTVB1 kernel for upstream ROCm/rocm-libraries filing. Note in docstring: script does not crash in a small process (wild addr lands on mapped VA); fault requires full ~19 GB model VA footprint. Wild addresses are always 2 MB-aligned across all 12+ production crashes. Also extends codespell skip list to include *.log (runtime output, not human-edited source — consistent with the existing comment in pyproject.toml). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dy to file GPU clean at session start; ROCm torch regression recurred (fixed make install-rocm). Wild address analysis: all 12 crashes are 2 MB-aligned, ~1.6 GB above B.end. Standalone reproducer written (scripts/repro_gfx1201_rocblas.py). Diagnostic SYNC+lvl3+probe run launched from checkpoint-1230 — parse log first next session. Next: parse diagnostic log, file AMD upstream issue, gpu-preflight, restart monitor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
replay-rocblas-bench.sh: expand constructed arg list via array (read -ra bench_args_arr) instead of unquoted $bench_args — SC2086. capture-rocblas-bench.sh: cd "$REPO_DIR" || exit 1 — SC2164. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A prose comment beginning with '# shellcheck' is read by shellcheck as a malformed directive (SC1072/SC1073), failing the pre-commit hook. Reword it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The crawl monitor was posting one PR comment per crash, flooding PR #101 (code review) with retry spam. Route all events to a dedicated Training Log issue (#120) instead, and collapse them into ONE pinned, self-updating comment rather than a comment per event. - ISSUE_NUMBER=120; new LOG_COMMENT_ID pins the live-log comment (bump it per new crawl, same convention as WANDB_RUN_ID). log_row fetches that comment, appends a row, and PATCHes it back — the comment body is the source of truth, so restarts / outputs/ wipes never lose history or mint a second comment. - Add a progress row every PROGRESS_EVERY=50 steps with an ISO-8601 timestamp + loss / grad_norm / lr / epoch parsed from the HF logger line (ast.literal_eval). Gated on a real metrics line so startup shard/dataset bars can't trigger a spurious row. - crash / STALLED / COMPLETE become table rows; one-line fault signature in the Note cell (pipes escaped), full tracebacks stay in crashlogs/. - HANDOFF_STAGE2_CRAWL.md updated to the single-comment model. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Running the crawl monitor (latest) + crash diagnosisThe monitor now posts to a single self-updating comment on the Training Log issue (#120) instead of spamming this PR per crash. Progress rows land every 50 steps (time / loss / grad_norm / lr); crash, STALLED, and COMPLETE are rows in the same table. Landed in 1. Get the latest monitor onto the training hostThe host keeps running whatever version was launched — editing/pulling does not change a running monitor. So pull, then relaunch: cd <repo-on-host>
git pull # must include e9ae500
pgrep -af 'monitor_stage2\.sh' # is an OLD one still running? kill it first:
# pkill -f 'monitor_stage2\.sh'2. Launch itnohup bash scripts/monitor_stage2.sh > outputs/monitor_stage2.log 2>&1 &
tail -f outputs/monitor_stage2.logIt launches training itself ( Every launch is GPU-gated — including the first. Before
3. If it crashes and nothing posts — diagnose on the hostNothing posting almost always means the monitor process wasn't running when training faulted (a silent crash), not a bad fault. Run: cd <repo-on-host>
pgrep -af 'monitor_stage2\.sh' # monitor alive?
pgrep -af 'train_sft\.py' # training alive?
tail -40 outputs/monitor_stage2.log # where the monitor stopped / gh WARNINGs
tail -60 outputs/sft-stage2-gemma4-31b/train.log # the actual fault
ls -lt outputs/sft-stage2-gemma4-31b/crashlogs/ | head # did it archive this crash?
gh auth status # token still valid on the host?Reading it:
Per-crash full tracebacks + dmesg snapshots are kept in |
- scripts/repro_gfx1201_dense.py: improved standalone reproducer that pre-allocates ~20 GB GPU filler before placing the Linear4bit layer, increasing VA density to match production and making the wild address more likely to land in a page-not-present region. Adds backward pass (production faults in both forward and backward GEMMs). - docs/HANDOFF_STAGE2_CRAWL.md: session 8 update — SYNC log parsed (fault 0x7efb33000000 2MB-aligned, MT64x64x64_ISA1201_DTVB1 confirmed), crawl progressed to checkpoint-1290 (26.7%), all processes killed, GPU dirty. Immediate action is gpu-preflight + monitor restart. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tor running Both system (/opt/rocm) and torch-bundled rocBLAS libraries dispatch MT128x128x32 identically for all probe variants; tile selection divergence (standalone vs production) remains unexplained at the binary level. Monitor active at step 1305/4826. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tqdm uses \r to overwrite its bar in-place; when the process crashes,
"Aborted (core dumped)" appends directly to the last bar state without
a newline, so grep -iE "aborted" matches the whole tqdm line and the
raw bar flooded the GitHub markdown table cell.
Fix: awk -F'\r' '{print $NF}' takes the last \r-separated chunk (the
final bar state + the error suffix), then sed strips any residual
%|...|[...] bar prefix before the pipe-to-fullwidth substitution.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add .hf_last_push persistence file so resumed runs skip already-uploaded checkpoints (src/project/hf_callback.py) - Add on_init launch-push: scans disk for latest checkpoint, pushes synchronously if not recorded as uploaded — catches crash mid-push - Fix numeric-sort bug in _push_latest_on_launch (was sorting alphabetically, e.g. checkpoint-90 > checkpoint-100) - Add step <= last_pushed guard in _maybe_push (force=True bypasses) - Set TRANSFORMERS_VERBOSITY=error in train_sft.py main() — suppresses level-3 INFO noise during full SFT runs - 9 new unit tests (7 → 16) covering persistence, skip-already-pushed, force-push bypass, on_init launch push / skip / numeric ordering - Update HANDOFF_STAGE2_CRAWL.md with new push behavior docs
After each successful checkpoint push to HF, the callback downloads the existing SFT_STAGE2B_CHECKPOINT_LOG.md (or starts fresh), adds/updates a row for the current checkpoint, and re-uploads it. Rows are keyed by step number so re-pushes replace rather than duplicate. - _push() signature extended with epoch, loss, acc parameters - _build_log_row() formats a table row - _update_checkpoint_log() downloads, merges, sorts, uploads the log - hf_hub_download failure = fresh file; upload failure caught and printed (non-critical, checkpoint already on HF) - 5 new tests: row formatting (with/without metrics), fresh-start, append, replace
monitor_stage2.sh: last_step() and crash_hint() were picking up the last N/total pattern in train.log, which was the eval tqdm bar (8578/8578) rather than the training-step bar — causing every crash row to report the wrong step. Filter out N==N patterns (100%-complete bars) so only in-progress training tqdm is matched. hf_callback.py: _push_latest_on_launch was pushing whatever checkpoint was latest on disk at resume time (e.g. step 1380, 1670) regardless of the push_every cadence, polluting the HF repo and the checkpoint log table with non-multiple-of-50 entries. Add the same push_every guard. Also fix a double-newline in the log table assembly that broke GFM rendering (LOG_HEADER trailing \n + join \n = blank line mid-table). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace tqdm/log-scraping progress trigger with checkpoint-based one: fire when latest_ckpt_step() hits a new multiple of PROGRESS_EVERY (50) and read epoch/loss/acc/grad_norm/lr from trainer_state.json instead of parsing train.log. This eliminates the 8578/8578 false-trigger and adds mean_token_accuracy to the issue #120 log table, matching the detail level of SFT_STAGE2B_CHECKPOINT_LOG.md on HF. Removes the now-unused current_step_num / latest_metrics / parse_metrics / log_progress helpers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Next experiments — M-sweep to crack the standalone repro (instructions)Session 11 (full writeup in #113) found the SYNC-deterministic log records the faulting M as 607, not 608. Every standalone probe so far used M=608 — the even neighbor, from the less-reliable async capture. A second SYNC run faulted at M=439. Both are odd; the tile divergence (standalone → Run on the training box (R9700), not the dev box. One GPU — do NOT run while 1. Sweep M (PRIMARY — minutes)make gpu-preflight
AMD_SERIALIZE_KERNEL=3 HIP_LAUNCH_BLOCKING=1 AMD_LOG_LEVEL=3 TORCH_USE_HIPBLASLT=0 \
uv run --no-sync python scripts/probe_gfx1201_tile.py --shape-3d --ckpt \
--m-sweep 439,512,576,606,607,608,609,640 2>&1 | tee probe_msweep.log
# which tile did each M dispatch?
grep -E 'SWEEP M=|MT[0-9]+x[0-9]+x[0-9]+.*ISA1201' probe_msweep.log \
| grep -oE 'SWEEP M=[0-9]+|MT[0-9]+x[0-9]+x[0-9]+'Read the result:
2. If an M dispatched MT64x64x64 — attempt the standalone crashRe-run the dense-VA repro ( 3. If no M dispatched MT64x64x64 — test the epilogue path... uv run --no-sync python scripts/probe_gfx1201_tile.py --shape-3d --ckpt --m 607 --no-bias 2>&1 | tee probe_nobias.log
... uv run --no-sync python scripts/probe_gfx1201_tile.py --shape-3d --ckpt --m 607 2>&1 | tee probe_bias.logIf Probe changes (already committed)
Upstream report corrections (#7992)
|
…p/--no-bias The SYNC-deterministic log records the faulting GEMM's A operand as (1, 607, 21504) — M=607, not the 608 every prior standalone probe used (608 came from the less-reliable async capture; a second SYNC run faulted at M=439). M is the sample's token length, so the standalone-vs-production tile divergence (MT128x128x32 vs MT64x64x64) may just be an off-by-one M crossing a Tensile size predicate. - default --m -> 607 - --m-sweep <csv>: run multiple M in one process, banner per M, to find which M dispatches MT64x64x64 - --no-bias: test the fused …_Bias_…_UserArgs_… epilogue hypothesis See #113 (Session 11) and #101 for the run plan. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
log() piped through tee -a SELF_LOG while stdout was also redirected to the same file at launch, writing every line twice. Switch to a direct append so there is only one writer. last_step() grepped train.log for N/total tqdm patterns and picked up dataset-tokenisation bars (8578 per shard) instead of training-step bars (max_steps=4826) because the training tqdm never appears in train.log. Replace the tqdm parse in the training-running branch with latest_ckpt_step(), which is accurate to within save_steps=10 and always reflects real forward progress. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes #100
Summary
caching_allocator_warmupon HIP/ROCm to suppress "Found no NVIDIA driver" raised by the CUDA-only allocator warmup in recenttransformersdevice_map={"": 0}for the whole QLoRA path so bitsandbytes can place all layers on the single GPU ("auto"can dispatch layers to CPU, which bnb rejects). Covers both live quantization and the pre-quantized dynamic 4-bit unsloth checkpoint (which keeps embeddings/lm_head/gates in 16-bit)unsloth/gemma-4-31B-it-unsloth-bnb-4bitcheckpoint (issue Investigate gfx1201 (RDNA4) training: Flash Linear Attention + Gemma4-31B QLoRA OOM #100): downloads already-NF4 (~19 GB), so it skips the ~62 GB BF16 CPU staging at load that was the R9700's actual blocker — no L40S prequant + rsync neededmake train-gemma4-31b-stage2-unsloth(TRAIN_PREQ=true+ unsloth base) and document the prequant vs live-quant launch paths intrain-sft-stage2-gemma4-31b.envTest plan
--dry-runvalidates the unsloth 31B config plumbing (config parse, LoRA regex, SFTConfig, data load) on the dev boxmake test-gpu-stackpasses on gfx1201 (confirms bnb 4-bit runtime) — already 13/13 per Investigate gfx1201 (RDNA4) training: Flash Linear Attention + Gemma4-31B QLoRA OOM #100make train-gemma4-31b-stage2-unslothloads without "Found no NVIDIA driver" on ROCmHuggingFace Model
Gemma4-31B-it-bnb-4bit
🤖 Generated with Claude Code