Join the conversation

Join the community of Machine Learners and AI enthusiasts.

Sign Up
bbidpa 
posted an update 3 days ago
Post
50
Diffs vs. Whole Files: How Should an LLM Edit Code?

New paper comparing two ways to train a code model to edit files: rewrite the whole file in one shot, or emit a sequence of small diffs, like a human dev would.

I invite you to try it live: 🤗 bbidpa/diffs-vs-whole-files-demo - throw in your own snippet and watch all four models race.

Trained two architectures (a from-scratch 100M model and a fine-tuned Qwen2.5-Coder-0.5B) both ways, on ~1,790 Flutter/Dart tasks.

Result: direct generation wins overall - but diffs hold their own on short, localized edits like refactors and bug fixes.

Diffs vs. Whole Files: An Empirical Comparison of Iterative Edit-Based and Direct Generation for Flutter/Dart Code Models (2609.05779)
bbidpa/Rainbow-Pony-100m-Flutter-steps
bbidpa/Rainbow-Pony-100m-Flutter-direct
bbidpa/Qwen2.5-Coder-0.5B-Flutter-steps
bbidpa/Qwen2.5-Coder-0.5B-Flutter-direct

About half your direct-minus-steps gap looks like the applier, not the representation.

Pulled both Rainbow-Pony eval sets and joined on sample_id, 1,789 tasks in both arms, so every comparison below is paired on the same task.

873 of 1,789 steps rows (48.8%) needed at least one ambiguous-match fallback hunk.

subset            n     steps dart_pass   direct dart_pass
fallback used   873          0.1214            0.7824
no fallback     916          0.5611            0.8210
all            1789          0.3466            0.8021

The control is the direct arm, because it never touches the applier. Across the same split it moves 3.9 points, 0.8210 to 0.7824. The steps arm moves 44.0. So the split is not task difficulty, or direct would fall with it.

If every row behaved like the no-fallback rows, steps reads 0.5611 instead of 0.3466. That closes 47.1% of the 45.6-point gap your headline rests on.

The part I did not expect: the fallback never reports failure.

                   applied   apply_failed   malformed
fallback action      2433          3            0        99.88% applied
clean-match action  12989        113            4        99.11% applied

A guessed hunk applies more reliably than a clean one. Nothing in stop_reason, outcome or the trajectory tells the model or the harness that a location was picked by first-occurrence. It only shows up 44 points later in dart_pass.

And it is structural, not a random slip. 2,365 of 2,436 fallback actions (97%) are insertions:

add_widget 1222   add_field 430   add_method 330   add_class 154
add_argument 147  add_code 51     add_constructor 30   add_import 1
update_method_body 71

Inserting into nested Dart anchors on a token that occurs many times per file. forms_and_validation needs the fallback on 80.9% of rows and scores 0.0928; refactoring_edits needs it on 24.0% and scores 0.5300. Your best steps category is the one where the applier rarely has to guess.

Smaller, on the similarity metric only.

I reproduced your scorer exactly, SequenceMatcher(None, output_code, final_code).ratio(), zero error against your column on 40 rows. Then scored the do-nothing policy, output = initial_code:

category            do-nothing   steps
refactoring_edits      0.3809    0.5601
basic_widgets          0.0184    0.4293

Overall do-nothing is 0.1937, so refactors start about twice as close to the answer as the average task. Normalize by remaining headroom and refactoring_edits drops from rank 1 of 9 to rank 5 of 9, and basic_widgets becomes the steps arm's best category.

On dart_pass your claim survives cleanly, refactors really do have the smallest arm gap at 0.230. It is the similarity framing that is carrying a starting-point effect.

The cheap experiment: make an ambiguous match a hard failure instead of a first-occurrence guess, and re-score. Does steps land near 0.56, or do those rows just move into apply_failed?