Skip to content

feat: add embedded adapters (granite switch) to openai backend#881

Merged
jakelorocco merged 23 commits into
mainfrom
jal/granite-switch-intrinsics
Apr 29, 2026
Merged

feat: add embedded adapters (granite switch) to openai backend#881
jakelorocco merged 23 commits into
mainfrom
jal/granite-switch-intrinsics

Conversation

@jakelorocco

@jakelorocco jakelorocco commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Misc PR

Type of PR

  • Bug Fix
  • New Feature
  • Documentation
  • Other

Description

  • Link to Issue: Fixes N/A

Adds support for granite switch models for OpenAIBackends.

Changes:

  • OpenAIBackends can utilize EmbeddedIntrinsicAdapters with any of the regular intrinsic functionality.
  • Adapters are loaded by default when load_embedded_adapters=True at init.
  • Added a placeholder IBM_GRANITE_SWITCH_4_1_8B that points to the temp repo for now.
  • Added documentation, examples, tests.
  • When generating from an intrinsic, we now only grab temperature and seed from model options. This is also correctly noted.
  • call_intrinsic function now works with both OpenAIBackends and LocalHFBackends

Please see most recent comment below for additional context: #881 (comment)

Testing

  • Tests added to the respective file if code was changed
  • New code has 100% coverage if code as added
  • Ensure existing tests and github automation passes (a maintainer will kick off the github automation when the rest of the PR is populated)

Attribution

  • AI coding assistants used

lastrasl@us.ibm.com;4A8621897;Luis Lastras and others added 3 commits April 10, 2026 18:11
Enable calling intrinsics on Granite Switch models via the OpenAI backend.
Granite Switch models embed adapter weights directly in the checkpoint and
activate them via chat template control tokens, so no PEFT loading is needed.

- Add EmbeddedIntrinsicAdapter class that carries only I/O config (no weights)
  with factory methods to load from a model directory or HuggingFace Hub
- Add register_granite_switch_model() and add_embedded_adapter() to OpenAIBackend
- Add _generate_from_intrinsic() that reuses IntrinsicsRewriter/ResultProcessor
  and injects intrinsic_name into chat_template_kwargs for the switch model
- Ensure serialized messages always include 'role' (latent issue in rewriter
  instruction messages, newly exposed by OpenAI API serialization path)
- Add unit tests for adapter loading, registration, and rewriting

Signed-off-by: lastrasl <lastrasl@us.ibm.com>
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS
@github-actions github-actions Bot added the enhancement New feature or request label Apr 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

The PR description has been updated. Please fill out the template for your PR to be reviewed.

@jakelorocco

Copy link
Copy Markdown
Contributor Author

I'm still in the process of testing some of these changes from an e2e perspective. I also still need to add documentation for how to use embedded models along with examples.

These changes do seem to work though and allow utilizing intrinsics with an embedded adapter / granite switch model.

@jakelorocco jakelorocco changed the title feat: add embedded adapters to openai backend feat: add embedded adapters (granite switch) to openai backend Apr 17, 2026
@lastras

lastras commented Apr 19, 2026

Copy link
Copy Markdown

Bug: model_options temperature is silently dropped for intrinsic calls

The seed parameter is correctly forwarded to api_params at line 639, but temperature (and any other model_options entries) are not. They are written into request_json at line 617, which gets absorbed into the ChatCompletion object by rewriter.transform(), but never extracted back out when building api_params. The result is that callers passing model_options={ModelOption.TEMPERATURE: 0.0} get the model's default temperature instead — causing non-deterministic outputs even when greedy decoding is explicitly requested.

Fix: mirror the seed pattern immediately after line 640:

if ModelOption.TEMPERATURE in model_options:
    api_params["temperature"] = model_options[ModelOption.TEMPERATURE]

Verified: with this fix, 5 back-to-back pipeline runs (guardian → rewrite → answerability → QC → base model, no sleep between calls) produce bit-for-bit identical outputs including long-form base model answers. Without the fix, outputs vary run-to-run despite temperature=0.0 being passed by the caller. The warning at line 557 ("some model options may be overwritten / ignored") is technically accurate but understated — temperature is always silently dropped, not just sometimes.

@nrfulton

Copy link
Copy Markdown
Member

@lastras Your comment states that all model options get dropped but that the fix is to add (another) special case for temperature in model_options.

It sounds from your comment like there's an underlying bug here that should be fixed. Can you clarify?

@jakelorocco

Copy link
Copy Markdown
Contributor Author

@lastras @nrfulton, I am going to move this convo to slack for faster resolution.

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS
@jakelorocco jakelorocco self-assigned this Apr 20, 2026
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS
@jakelorocco
jakelorocco force-pushed the jal/granite-switch-intrinsics branch from c0b163c to 3227a5b Compare April 20, 2026 18:20
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
@jakelorocco

Copy link
Copy Markdown
Contributor Author

I fixed a few random issues and also the issues we talked about (call_intrinsic and seed/temperature setting). I'm working on adding some documentation and minor examples.

The functional parts of the code should be stable now. I have additional things that I'd like to clean up, but I will delay them until after we make sure everything is working as intended and post-release, so I don't impact any functionality.

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS
@jakelorocco

jakelorocco commented Apr 20, 2026

Copy link
Copy Markdown
Contributor Author

Updated the PR description. Fixed a few additional errors / bugs. Added documentation and examples. We should be good to start reviewing. I need to run one more round of tests with the latest from the granite-switch team.

I have plans to open a few additional issues to clean things up after this PR is merged.

Additional changes that should be made before the official release:

  • Update IBM_GRANITE_SWITCH_4_1_3B to point to the correct model and update all references to the model
  • Update our documentation to point to the official granite switch documentation when published

@jakelorocco
jakelorocco marked this pull request as ready for review April 20, 2026 21:45
@jakelorocco
jakelorocco requested a review from a team as a code owner April 20, 2026 21:45
@psschwei

Copy link
Copy Markdown
Member

Docs pointer

Docs should not be a blocker for merging this btw, just something we should have in advance of the release.

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
@jakelorocco
jakelorocco force-pushed the jal/granite-switch-intrinsics branch from fb957fe to caec848 Compare April 26, 2026 14:16
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS
Assisted-by: CLAUDE:OPUS
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
…r from where the adapters are loaded from

Assisted-by: CLAUDE:OPUS
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>

@planetf1 planetf1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM - I know there's some placeholders here such as for the repo -- but I wonder if the doc update should be deferred until later, since this will get published on our next minor release (Wed) and would have a dead line (openai.md and glossary.md) ?

@jakelorocco

Copy link
Copy Markdown
Contributor Author

We can defer doc updates. There was concern about the model_identifier being left in with the placeholder value. I'm open to merging with both the docs and placeholder model (Grizlee-Ber/...) in the code, but was hoping we could fix those before merging.

@ajbozarth

Copy link
Copy Markdown
Contributor

I'm open to merging with both the docs and placeholder model (Grizlee-Ber/...) in the code, but was hoping we could fix those before merging.

I agree that waiting for the fixes was my preference, but if leaving this unmerged is causing code drift we may want to merge now then fix those before the next release

@lastras

lastras commented Apr 27, 2026 via email

Copy link
Copy Markdown

@jakelorocco

Copy link
Copy Markdown
Contributor Author

@planetf1 @ajbozarth @avinash2692, can I please get a review from one (or more) of y'all? We are going to merge as is (assuming you don't spot any issues with the code). Then, pre-release, we will update the model id and doc links.

@ajbozarth ajbozarth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two non-blocking nits below, otherwise LGTM.

Comment thread mellea/backends/openai.py
Returns:
list[str]: Names of the registered intrinsics.
"""
import os

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

import os is never used inside this function (os is already imported at module level on line 7). Ruff will flag this as F401 — safe to remove.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will address in a follow up PR. Thank you.

Comment thread mellea/backends/openai.py
Comment on lines +572 to +574
if len(model_options.items()) > 0:
MelleaLogger.get_logger().info(
"passing in model options when generating with an intrinsic; only temperature and seed are kept from model options"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This fires even when the caller passes only recognized options like {ModelOption.TEMPERATURE: 0.0}. Consider narrowing to unrecognized options only so it's not a false alarm:\npython\nrecognized = {ModelOption.TEMPERATURE, ModelOption.SEED}\nif any(k not in recognized for k in model_options):\n MelleaLogger.get_logger().info(...)\n

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will address in a follow up PR. Thank you.

@jakelorocco
jakelorocco added this pull request to the merge queue Apr 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Apr 29, 2026
@jakelorocco
jakelorocco added this pull request to the merge queue Apr 29, 2026
Merged via the queue into main with commit d59223e Apr 29, 2026
8 checks passed
@jakelorocco
jakelorocco deleted the jal/granite-switch-intrinsics branch April 29, 2026 17:39
planetf1 added a commit to planetf1/mellea that referenced this pull request May 7, 2026
- Title and prose shift to "adapter" as primary term; "intrinsic" noted
  as legacy vocabulary with a rename-scope decision in Part I §5 Q6.
- Reality B clarified: weights ship in the same HF repo as the base
  model (not "baked in"); both LoRA and aLoRA supported via the
  technology field on each embedded adapter.
- Reality C reframed: OpenAI-compatible support is already kept for
  embedded (PR generative-computing#881); the gap is non-embedded server-mediated, which
  may or may not re-land in 0.6.0.
- Add §10.1 "Why adapters need bespoke observability" with four
  concrete failure modes, two already seen in production.
- Goals elevate customer-built adapters as a first-class target.
- Tests section adds an optional per-adapter qualitative effectiveness
  suite (opt-in via @pytest.mark.qualitative).

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
akihikokuroda pushed a commit to akihikokuroda/mellea that referenced this pull request May 27, 2026
…ative-computing#881)

* feat: add Granite Switch embedded intrinsic support to OpenAI backend

Enable calling intrinsics on Granite Switch models via the OpenAI backend.
Granite Switch models embed adapter weights directly in the checkpoint and
activate them via chat template control tokens, so no PEFT loading is needed.

- Add EmbeddedIntrinsicAdapter class that carries only I/O config (no weights)
  with factory methods to load from a model directory or HuggingFace Hub
- Add register_granite_switch_model() and add_embedded_adapter() to OpenAIBackend
- Add _generate_from_intrinsic() that reuses IntrinsicsRewriter/ResultProcessor
  and injects intrinsic_name into chat_template_kwargs for the switch model
- Ensure serialized messages always include 'role' (latent issue in rewriter
  instruction messages, newly exposed by OpenAI API serialization path)
- Add unit tests for adapter loading, registration, and rewriting

Signed-off-by: lastrasl <lastrasl@us.ibm.com>

* feat: refactor embedded adapters

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS

* fix: issues with qualitative openai intrinsics tests

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS

* fix: minor refactoring

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>

* fix: call_intrinsic function to work with other adapter types

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS

* fix: more improvements to adapters and call_intrinsic

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS

* fix: explicitly extract seed and temp from model options for intrinsics

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS

* fix: add tests specifically for openai backend and call_intrinsic flow

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS

* fix: rename openai init param to load_embedded_adapters

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>

* fix: pre-commit issues

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS

* feat: add model id for granite switch

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>

* feat: add examples and documentation for granite-switch in mellea

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS

* fix: update pyproject dependencies for openai to support granite-switch

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS

* fix: pre-commit issues

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS

* fix: pr issues

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS

* fix: changes to granite-switch structure

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS

* fix: allow call_intrinsic to use any type of adapter

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>

* fix: test_openai_intrinsics expected results

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>

* fix: add tests cases and fix missed function signature changes

Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Assisted-by: CLAUDE:OPUS

* fix: auto-loading embedded adapters for local model dirs

Assisted-by: CLAUDE:OPUS
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>

* fix: allow specifying an adapter_source so that model names can differ from where the adapters are loaded from

Assisted-by: CLAUDE:OPUS
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>

---------

Signed-off-by: lastrasl <lastrasl@us.ibm.com>
Signed-off-by: Jake LoRocco <jake.lorocco@ibm.com>
Co-authored-by: lastrasl@us.ibm.com;4A8621897;Luis Lastras <lastrasl@p6-r05-n3.bluevela.rmf.ibm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants