Skip to content

feat!: wire flashAttn up for real and follow the lazyMode rename - #198

Merged
bernardladenthin merged 3 commits into
mainfrom
claude/flash-attn-wiring
Sep 1, 2026
Merged

feat!: wire flashAttn up for real and follow the lazyMode rename#198
bernardladenthin merged 3 commits into
mainfrom
claude/flash-attn-wiring

Conversation

@bernardladenthin

@bernardladenthin bernardladenthin commented Sep 1, 2026

Copy link
Copy Markdown
Owner

⚠️ Red until bernardladenthin/java-llama.cpp#408 ships

This depends on net.ladenthin:llama:5.2.0-SNAPSHOT, which is not published. CI will fail to resolve the dependency, and that is expected — the CHANGELOG says so too, so a red build here is not mistaken for a regression. Merge order: jllama#408 → a jllama release or snapshot deploy → this.

1. flashAttn works

The knob has been settable and non-functional since it was introduced. --flash-attn takes a mandatory on|off|auto; the binding offered only a bare-flag setter, so emitting the key alone made llama.cpp's parser consume the following argv token and the load died naming a flag the user never set. 1.2.0 shipped a refusal at plan time rather than that diagnostic — the honest interim answer, not the fix.

jllama 5.2.0 adds ModelParameters.setFlashAttn(FlashAttn), so the provider now emits --flash-attn on when the knob is set. false keeps emitting nothing, leaving llama.cpp's own auto default in force — what an unconfigured run should get, and consistent with every other optional knob's sentinel here.

Both refusals and the three tests pinning them are gone. DEFAULT_FLASH_ATTN's javadoc stops describing an unusable knob but keeps the history: the reason the value matters is exactly why the knob spent a release refused.

2. tensorReadLazylazyMode — breaking

llama.cpp b10731 renamed --tensor-read-lazy to -lzm / --lazy-mode with no alias, and jllama followed the rename rather than papering over it. So the model-definition field, its accessors, the LlamaCppJniConfig accessor and builder, the factory mapping, module-info, the plugin README row and the sweep case all move. Carrying the old name would leave the configuration surface describing a flag that does not exist.

The part that is more than bookkeeping

flashAttn moves out of LlamaCppJniKnobSweepTest's NOT_SWEPT list into a real sweep case, so it is now set against a real model on every run. Until now only the refusal had ever been executed — closing the removed TODO's own condition ("actually set the knob against a real model once"). The sweep's completeness check is what forced this to be resolved rather than quietly left excluded.

And the sweep found its second defect while this PR was being written. After the rename, case [20] (lazyMode) went red with Failed to parse model parameters — a new Java name paired with an old native, which fails only at load time and which nothing earlier in the pipeline can see. It went green once the jar carried a libjllama.so actually built from the pinned tag. That mismatch is a real hazard on every future rename, and it is now demonstrably caught.

Test plan

Verified against a real binding, not assumed: jllama#408 installed into the local repository, with a libjllama.so built from b10731 with all eight patches applied (cmake configure clean, full -O3 build exit 0, NativeLibraryLoadSmokeTest 4/4 including the pin cross-check).

  • Core module: 637 tests, 0 failures, 0 skipped — including the full knob sweep against that native
  • LlamaCppJniKnobSweepTest — every knob at a non-default value, flashAttn(true) and lazyMode("on") among them
  • mvn compile clean under Error Prone / NullAway / Checker Framework; spotless:apply run
  • CI is green on this branch — no, and deliberately: see the banner above

Related issues / PRs

Consumer side of bernardladenthin/java-llama.cpp#408. Closes the flashAttn entry in TODO.md, removed here.

Checklist

  • I have read CONTRIBUTING.md and CODE_OF_CONDUCT.md
  • My commits follow Conventional Commits (feat! + BREAKING CHANGE trailer)
  • No security-sensitive changes

The knob has been documented and settable since it was introduced, and could
never be forwarded. --flash-attn takes a mandatory on|off|auto, the binding
offered only a bare-flag setter, and emitting the key alone made llama.cpp's
parser consume the next argv token -- so the load died naming a flag the user
never set. 1.2.0 shipped a refusal at plan time rather than that diagnostic.

net.ladenthin:llama 5.2.0 adds ModelParameters.setFlashAttn(FlashAttn), so the
provider now emits --flash-attn on when the knob is set. false keeps emitting
nothing, which leaves llama.cpp's own auto default in force: that is what an
unconfigured run should get, and it is what every other optional knob here
does with its sentinel.

Both refusals go, along with the three tests that pinned them, and the
DEFAULT_FLASH_ATTN javadoc stops describing a knob that cannot be used. The
history is kept in that javadoc rather than deleted -- the reason the value
matters is exactly why the knob spent a release unusable.

The part that is more than bookkeeping: flashAttn moves out of the knob
sweep's NOT_SWEPT list into a real sweep case, so it is now set against a real
model on every run. Until now only the refusal had ever been executed, which
is what the removed TODO asked for. 36 sweep cases green, plus EngineSupport,
AiGenerationConfig and the factory mapping -- 105 tests.

llama.version moves to 5.2.0-SNAPSHOT deliberately: the binding change is not
published yet, so CI stays red until it is. The CHANGELOG says so, so a red
build is not mistaken for a regression.
@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown

Code Review Summary

This is a well-executed fix that finally makes the flashAttn knob functional. The changes are clean and comprehensive:

✅ Strengths

  1. Clean removal of workarounds: The plan-time validation guard (validateFlashAttnIsNotRequested) and provider-side refusal with FLASH_ATTN_UNSUPPORTED_MESSAGE are completely removed, simplifying the codebase.

  2. Correct use of the new binding API: The change from the bare-flag enableFlashAttn() to the value-taking setFlashAttn(FlashAttn.ON) is the right fix. The enum import is correctly added alongside existing similar imports like CacheType.

  3. Excellent test coverage: Moving flashAttn from NOT_SWEPT into a real parametrized test case means it's now actually tested against a real model — this is exactly what was missing before. The related test removals are appropriate since they were pinning the refusal behavior.

  4. Comprehensive documentation:

    • The CHANGELOG entry clearly explains both the bug and the fix
    • The AiGenerationConfig javadoc updates provide useful context about why the knob matters and the history of the issue
    • The TODO cleanup makes it explicit that this is resolved
  5. Transparent about snapshot dependency: The clear banner in the PR description and CHANGELOG note about depending on llama:5.2.0-SNAPSHOT sets proper expectations for CI.

🔍 Observations

  • Import cleanup: Unused imports (containsString, Assertions) are correctly removed from test files
  • Backward compatibility: Setting false still emits nothing (letting llama.cpp use its own auto default), maintaining the intended zero-config behavior

No bugs, security issues, or architectural concerns identified.

BREAKING CHANGE: the tensorReadLazy model-definition field and its accessors
are renamed to lazyMode.

llama.cpp b10731 renamed --tensor-read-lazy to -lzm / --lazy-mode with no
alias, and net.ladenthin:llama followed the rename instead of papering over
it. Keeping tensorReadLazy here would leave the configuration surface naming a
flag that no longer exists, so the field, its getters, the LlamaCppJniConfig
accessor and builder, the factory mapping, module-info, the plugin README row
and the sweep case all move.

The mismatch this guards against is not cosmetic, and it showed up while doing
the work: a new Java name paired with an old native fails at load time with
"Failed to parse model parameters" and nothing earlier sees it. The knob sweep
caught exactly that -- case [20] went red against a jar carrying the previous
native, and green once the jar carried one built from the pinned tag. That is
the second defect the sweep has found since it was written.

Core module: 637 tests, 0 failures, 0 skipped, run against a libjllama.so
built from b10731 with all eight patches applied.
@bernardladenthin bernardladenthin changed the title feat: wire flashAttn up for real, now that the binding can express it feat!: wire flashAttn up for real and follow the lazyMode rename Sep 1, 2026
@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown

Code Review Summary

This PR makes two coordinated changes: fixing the flashAttn knob wiring and renaming tensorReadLazy to lazyMode to match the upstream binding. Both are breaking changes properly documented in the CHANGELOG.

✅ Strengths

  1. Comprehensive refactoring — The tensorReadLazylazyMode rename is consistently applied across:

    • Configuration classes (AiGenerationConfig, AiModelDefinition)
    • Provider implementation and config factory
    • All tests with corresponding assertions
    • Constants and module descriptor
    • Plugin README
  2. Flash Attention fix is clean — The knob now properly wires through the binding's new ModelParameters.setFlashAttn(FlashAttn.ON) setter instead of refusing at plan time. The logic is sound: when true, emit the value; when false, emit nothing (letting llama.cpp's default take over).

  3. Test coverage maintained — The refusal tests are appropriately removed and flashAttn is moved from the NOT_SWEPT exclusion list into real sweep cases, so it's now tested against an actual model.

  4. Well-documented — The CHANGELOG entries explain the motivations clearly, including the hazard of name/binding mismatches being caught at load time.

✅ Correctness

  • Field renames are consistent (no partial migrations)
  • Imports updated correctly (TensorReadLazyModeLazyMode, added FlashAttn)
  • Enum resolution methods renamed and signatures updated (tensorReadLazyMode()lazyMode())
  • All test names and assertions updated to match
  • The plan-time and provider-side refusals are cleanly removed with their related tests

No issues detected

The code is ready once the net.ladenthin:llama 5.2.0-SNAPSHOT dependency is available. CI remaining red is expected and documented in the PR description.

The provider now calls setFlashAttn(FlashAttn) and setLazyMode(LazyMode), neither
of which exists in 5.1.0, so this module cannot compile against the released
binding any more.

Naming 5.2.0-SNAPSHOT would not help: this repo declares no <repositories>
element (distributionManagement/snapshotRepository is deploy-only), so the
Central snapshot channel is not even resolvable here -- and a -SNAPSHOT
dependency on main would block every srcmorph release, since Central rejects a
release that depends on one.

The build therefore stays red until net.ladenthin:llama:5.2.0 is published, and
goes green with no further edit the moment it is.
@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown

Review: Code Quality & Correctness

This is a well-executed PR. No issues found.

Strengths

  1. Complete refactoring — tensorReadLazy → lazyMode applied consistently across all modules
  2. flashAttn properly wired — Calls setFlashAttn(FlashAttn.ON) instead of throwing
  3. Test coverage — flashAttn moved to active sweep case; test infrastructure cleaned up
  4. Breaking changes documented — feat! trailers and CHANGELOG explain the binding upgrade
  5. Dependency strategy transparent — Pinned to 5.2.0 release with detailed explanation

Verification

✓ Null safety annotations present
✓ Imports updated correctly
✓ Enum mapping mirrors existing patterns
✓ Code style aligns with CLAUDE.md
✓ No security issues detected

@bernardladenthin
bernardladenthin merged commit 6b26b38 into main Sep 1, 2026
18 of 27 checks passed
@bernardladenthin
bernardladenthin deleted the claude/flash-attn-wiring branch September 1, 2026 09:16
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.

2 participants