Skip to content

unsloth: unbreak the pin set on b10775 - #174

Merged
danielhanchen merged 13 commits into
masterfrom
pr-set/unbreak-nightly-b10775
Sep 4, 2026
Merged

unsloth: unbreak the pin set on b10775#174
danielhanchen merged 13 commits into
masterfrom
pr-set/unbreak-nightly-b10775

Conversation

@danielhanchen

Copy link
Copy Markdown
Member

The nightly has been failing since 09-02, and by b10775 five of the fourteen pins were broken. Run 33691388002 stopped at the first of them:

unslothai/llama.cpp#70 (edfd4c1a3) does not merge cleanly onto b10760 + the PRs listed before it

resolve stops at the first failure, so the other four were invisible. Each one is a different way for a pin to rot, and each needed a different fix.

pin what happened fix
unslothai#70 upstream landed the same SSM_A_NOSCAN change as ggml-org#28121, so the tip commit re-applied it and every merge was an edit/edit conflict refreshed onto b10775, took upstream's line, 883f2c9ba
unslothai#91 merged into fork master, so it cannot be repinned; its pin forked from a 08-10 base and the NGRID_* defines now sit on a line upstream has edited re-carried as #171, 95e08993a
ggml-org#25731 upstream's sparse attention and this PR's banded bias thread parameters through the same 13 places in fattn-mma-f16.cuh; the head repo is ggml-org, so the repin bot cannot touch it carried as #172, 5b836b6f1
ggml-org#27754 merges with no conflict at all and does not compile: ggml-org#27970 added n_kv_max to build_attn_mha and this PR's new build_attn_sparse still calls the old signature carried as #173, aed95865b
unslothai#154 upstream ggml-org#28040 deleted for_each_token_in, the function it optimises dropped, PR closed
ggml-org#28133 b10775 contains it; upstream squashed, so the pinned commit is not an ancestor and the merge re-applies code the base already has dropped

ggml-org#27754 is the one worth reading twice. It is the failure mode the pr-set doc warns about in a form nothing in the pipeline looks for: the merge is clean, additive_merge.py is never invoked, merge_checks.py passes, and the release only dies at compile time in the CUDA job, forty minutes and thirty-nine jobs later.

Verification

Replayed the whole set onto b10775 exactly the way the resolve step does, with the resolver from master rather than the one in #170, so this stands on its own:

clean     unslothai#107
clean     unslothai#172
additive  unslothai#70
clean     unslothai#171
clean     unslothai#95
clean     unslothai#173
clean     unslothai#137
clean     unslothai#158
clean     unslothai#157
clean     unslothai#149
clean     unslothai#144
clean     unslothai#152

Eleven clean, one additive. Then on the merged tree:

$ python3 scripts/unsloth/merge_checks.py --root .
merge_checks: scanned 23 python and 187 c++ files
merge_checks: clean

$ cmake -B build -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=100 -DLLAMA_BUILD_TESTS=ON
$ cmake --build build -j 96
[ 56%] Built target llama
[ 92%] Built target llama-quantize
[ 99%] Built target test-backend-ops
[ 99%] Built target llama-server
[100%] Built target llama-cli

CUDA sm_100, B200, CUDA 13.1. The pin lint passes with no errors; the only notes are the two below.

Two things left alone deliberately

unslothai#107 is closed, and the resolve log reads that as "upstream declined it". It did not: ggml-org#24423 (DiffusionGemma) is still open, and #107 is our own carry branch, which is closed rather than merged because that is what carry branches are for. The pin merges clean and ships work upstream has not rejected, so it stays. The warning text is what is wrong, not the pin.

unslothai#144 has moved one commit past its pin (b76199698, "qwen4exp mtp: trim comments"). It is comment-only as far as I can see, but repinning past a reviewed commit is the one thing the pin file exists to prevent, so that is a call for whoever reviews #144, not for this PR.

danielhanchen added 4 commits September 3, 2026 08:33
…nd it

additive_merge.py refuses a conflict when both sides add a line the other
also adds, on the grounds that one construct added twice would be duplicated
by a union. Two independent case arms in the same switch always share their
scaffolding, so that check fires on `{` and `} break;` and refuses exactly
the add/add it exists to resolve.

That is what stopped the 09-02 nightly on its last pin:

  refused tools/mtmd/clip.cpp: both sides add the same line(s),
  so this is one change made twice: {, } break;

where one side added a PROJECTOR_TYPE_KIMIK3 arm and the other a
PROJECTOR_TYPE_DEEPSEEK4V one, with no line of actual content in common.

Compare the sides on their identifying lines instead: braces, brackets,
parens, semicolons and commas around at most one bare block-terminating
keyword are scaffolding and carry no identity. `break;` is scaffolding,
`return true;` is not, and anything naming a type, constant or function is
not. Two arms that really are the same change still share their case label
and their body, so a genuine duplicate is still refused.

A side whose addition is nothing but scaffolding now refuses too: with the
scaffolding discounted there is no content left to tell the two additions
apart, so unioning would be a guess.
Discounting the braces is not enough on its own. Once upstream landed
DEEPSEEK4V, the KIMIK3 arm and the DEEPSEEK4V arm of the same switch both
set `hparams.rope_theta = 10000.0f;`, so the shared-line check refuses on a
coincidence:

  conflict tools/mtmd/clip.cpp: both sides add the same line(s),
  so this is one change made twice: hparams.rope_theta = 10000.0f;

Two arms of one switch labelled differently are two constructs, whatever
lines their bodies have in common, so when both sides add case arms and no
label appears on both sides, the union is the resolution.

The labels are the proof, and they are also what keeps the duplicate check
working: the same change made twice keeps its label, so it never reaches
this branch and is still refused. A duplicated label would not compile.
Five pins stopped merging. Two of them had been overtaken by upstream, one
was carrying work the base tag now has, one is not ours to fix, and one is
obsolete.

  ggml-org#25731 -> #172   carried onto b10775; the head repo is
                                    ggml-org, so the repin bot cannot touch it
  #70                      refreshed onto b10775
  #91  -> #171    merged into fork master, so it cannot be
                                    repinned; re-carried onto b10775
  #154                     dropped, upstream deleted the function
  ggml-org#28133                    dropped, b10775 contains it

The whole set now merges onto b10775 with eleven clean merges and one
additive one, and the merged tree passes merge_checks.py.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T01:21:11.159855Z b454748 New commits
🔒 Security Review Completed 2026-09-03T11:14:51.601443Z 9326b69 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@danielhanchen

Copy link
Copy Markdown
Member Author

test-backend-ops on the fully merged tree, all twelve pins on b10775, CUDA sm_100, B200:

14742/14742 tests passed
Backend CUDA0: OK
2/2 backends passed
OK

Every op, so the pins that add their own (LIGHTNING_INDEXER from #173, banded flash attention from #172, the IQ1 types from #171) are exercised against the CPU reference in the same tree as each other, which is the thing none of the individual PRs can check.

repin.py writes its new pins back into scripts/unsloth/pr-set.json, and
running it here to reproduce the failing conflict left that file modified.
It has nothing to do with this change, and the pins it wrote point at local
merge commits that were never pushed, so the lint correctly rejected them.

The pin changes live in their own PR.
@danielhanchen

Copy link
Copy Markdown
Member Author

Pushed three more pin changes, all of which came out of making the shipped features testable.

pin change
unslothai#107 -> unslothai#177 the DiffusionGemma carry was on a b10630-era base from 08-26. Re-carried onto b10775, and canvas_length now has an llm_kv id so the arch can be built by a test fixture at all.
unslothai#172 repinned to 2cd60b78: fixture params so test-llama-archs actually runs Inkling instead of printing SKIP.
unslothai#176 (new) the projector registry test, which covers every vision and audio tower at once with no model file.

Replayed onto b10775 again with the thirteen pins:

clean     unslothai#177
additive  unslothai#172
additive  unslothai#70
clean     unslothai#171
clean     unslothai#95
clean     unslothai#173
clean     unslothai#137
clean     unslothai#158
clean     unslothai#157
clean     unslothai#149
clean     unslothai#144
clean     unslothai#152
clean     unslothai#176

unslothai#172 is additive now because #177 and #172 both add a fixture block to test-llama-archs.cpp at the same place. The two blocks share exactly one line, }, so with the resolver on master this fails:

refused tests/test-llama-archs.cpp: both sides add the same line(s),
so this is one change made twice: }

That is the case #170 fixes, arrived at from a completely different direction than the one that motivated it. This PR now depends on #170, where it did not before.

Then on the merged tree, CUDA sm_100, B200:

$ cmake --build build -j 96
[ 56%] Built target llama
[ 91%] Built target test-llama-archs
[ 96%] Built target test-mtmd-impl
[ 99%] Built target test-backend-ops

$ python3 scripts/unsloth/feature_matrix.py --build-dir build --gpu
ok   glm5next: 2/3 device rows decoded and matched CPU; 144/144 cases matched the CPU reference
ok   inkling: 2/3 device rows decoded and matched CPU; 13/13 cases matched the CPU reference; projector registry intact over 194 assertions
ok   iq1-narrow-grids: 13/13; 13/13; 13/13 cases matched the CPU reference
ok   kimi-k3: 2/3 device rows decoded and matched CPU; projector registry intact over 194 assertions
ok   projector-registry: projector registry intact over 194 assertions
ok   qwen4exp-mtp: 2/3 device rows decoded and matched CPU; 4/4 cases matched the CPU reference

all 6 features demonstrated on GPU

That is the first time any of these has been shown to work in the composed tree rather than one PR at a time. feature_matrix.py and the manifest it reads are in a separate PR.

DiffusionGemma is the one exception and is recorded as such rather than quietly missing: the fixture is in place and the model builds, but the backend comparison aborts inside the arch's own graph input on a null self_kq_mask->buffer. Details in #177.

The base tag moved from b10775 to b10786 while this was open, and five
pins needed work to survive it. Four of the five merge without a single
conflict and produce a tree that does not compile, which is the failure
mode the compile gate in #175 exists for; three of those four are the
same upstream change.

- #172 inkling, #173 glm5next, #177 diffusion-gemma: n_ff_exp became a
  per-layer array behind an accessor. Reading the old scalar field is a
  compile error, and inside a create_tensor dimension list the member
  function quietly decays to a pointer-to-member instead. #172 and #173
  also override preprocess(), which the mtmd base classes made const.
- #152 per-run buffers: b10786 added a load-ordering pass that reads a
  llama_buf_map entry as one buffer, and this pin made an entry a run of
  buffers.
- #144 qwen4exp MTP: the only one that conflicts, in both places it
  touches, over the same n_ff_exp change.

Verified on b10786: all 13 pins merge (11 clean, 2 additive), the CPU
llama target builds, and the CUDA build plus the feature matrix are in
the PR comment.
@danielhanchen

Copy link
Copy Markdown
Member Author

The base tag moved to b10786 while this was open, and the preflight on this branch said so before I did:

- `unslothai/llama.cpp#172` (`2cd60b7852`) does not merge onto `b10786` + the pins before it.

Five pins needed work to survive the move. Four of the five merge without a single conflict and produce a tree that does not compile, which is the case the compile gate in #175 exists for, and three of those four are the same upstream change.

pin what b10786 changed conflict
#172 inkling n_ff_exp is a per-layer array behind an accessor; mtmd preprocess() is const none
#173 glm5next same, plus n_expert_used, plus const preprocess() none
#177 diffusion-gemma same n_ff_exp change none
#152 per-run buffers a new load-ordering pass reads a llama_buf_map entry as one buffer, and this pin made an entry a run of buffers none
#144 qwen4exp MTP the same n_ff_exp change, in both places it touches yes, both hunks

The n_ff_exp one is worth pointing at, because inside a create_tensor dimension list the old scalar field silently becomes a pointer-to-member rather than an obvious type error:

error: cannot convert 'llama_hparams::n_ff_exp' from type
       'uint32_t (llama_hparams::)(uint32_t) const' to type 'const int64_t'

Verified on b10786, with the exact pins listed here

All 13 merge: 11 clean, 2 additive. The two additive ones need #170, since #172 and #177 both add a fixture block to test-llama-archs.cpp and share exactly one line, }:

resolved src/llama-arch.cpp
resolved src/llama-arch.h
refused  tests/test-llama-archs.cpp: both sides add the same line(s), so this is one change made twice: }

That is master's resolver on tonight's base, not a constructed example.

pin_contract.py from #175, over the merged tree:

all 13 pins are intact in the merged tree

CUDA sm_100 build clean, and feature_matrix.py from #178 on a B200:

ok   glm5next: 2/3 device rows decoded and matched CPU; 144/144 cases matched the CPU reference
ok   inkling: 2/3 device rows decoded and matched CPU; 13/13 cases matched the CPU reference; projector registry intact over 194 assertions
ok   iq1-narrow-grids: 13/13; 13/13; 13/13 cases matched the CPU reference
ok   kimi-k3: 2/3 device rows decoded and matched CPU; projector registry intact over 194 assertions
ok   projector-registry: projector registry intact over 194 assertions
ok   qwen4exp-mtp: 2/3 device rows decoded and matched CPU; 4/4 cases matched the CPU reference

all 6 features demonstrated on GPU

The tree I built and ran that on is byte-identical to the tree these pins produce, so the run belongs to this pin set and not to a scratch copy of it.

danielhanchen pushed a commit that referenced this pull request Sep 3, 2026
…ten for

The every-pin-is-declared lint compares feature-checks.json against
pr-set.json on this branch, and master's set still lists the retired
pins, so the check fails on pins this manifest was never meant to cover.
Merging #174 in makes it compare against the real set; the diff goes
away when #174 lands.
danielhanchen added 2 commits September 4, 2026 00:42
The exclusion blamed the arch's graph input for a null self_kq_mask
buffer. It was the fixture: a scalar sliding-window pattern makes every
layer sliding, so the plain mask is built and used by nothing and never
gets allocated. With a per-layer pattern the arch decodes and matches
CPU on all three devices.
The preflight on this branch reports that #172 does not merge onto
b10786, and it is right: #172 and #177 both add a fixture block to
tests/test-llama-archs.cpp and share exactly one line, `}`, which
master's additive_merge.py reads as the same change made twice.

#170 fixes that and lands first. Merging it here so the preflight on
this branch tests the combination that will actually exist on master,
rather than a state nobody will ever run.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b454748037

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

ident = [ln for ln in nonblank(lines) if not STRUCTURAL.match(ln)]
if not ident or not CASE_LABEL.match(ident[0]):
return None
return {ln for ln in ident if CASE_LABEL.match(ln)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Parse case labels before testing disjointness

When both branches add the same one-line arm with different bodies (for example, case FOO: return 1; versus case FOO: return 2;) or merely format the label differently (case FOO: versus case FOO :), this set contains the entire lines, so the sets appear disjoint. resolve_region then unions them and stages a duplicate case FOO instead of refusing the conflicting implementations; the same problem occurs with differently implemented one-line default: arms. Extract and normalize only the label portion before applying the disjointness shortcut.

Useful? React with 👍 / 👎.

@danielhanchen
danielhanchen merged commit 07a7aa8 into master Sep 4, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant