Skip to content

Add structured ranking and decision runtime APIs - #2649

Merged
Akshay Sonawane (apsonawane) merged 38 commits into
mainfrom
asonawane/non-generative-runtime
Oct 29, 2026
Merged

Akshay Sonawane (apsonawane) merged 38 commits into
mainfrom
asonawane/non-generative-runtime

Conversation

@apsonawane

@apsonawane Akshay Sonawane (apsonawane) commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Add production-oriented non-generative inference for multi-component CLM ranking and KEV decision packages without routing them through the autoregressive Generator API.

The public surface is intentionally small: one package-selected session, configured through the existing OgaConfig, with a JSON request/response boundary.

Public API

The C ABI adds three exports:

OgaCreateNonGenerativeSession(
    const OgaConfig* config,
    OgaNonGenerativeSession** session);
OgaDestroyNonGenerativeSession(OgaNonGenerativeSession* session);
OgaNonGenerativeSessionRunJson(
    OgaNonGenerativeSession* session,
    const char* request,
    size_t request_length,
    const char** response);

The C++ API provides one move-only RAII wrapper:

NonGenerativeSession session{config};
std::string response = session.Run(request_json);

Python follows the same model:

config = onnxruntime_genai.Config(package_path)
session = onnxruntime_genai.NonGenerativeSession(config)
response = session.run(request)

OgaConfig remains the source of truth for the package path, providers, provider options, hardware selection, overlays, session options, tokenizer settings, pad token, and context length. The session copies the effective configuration during construction.

Supported workflows

CLM free ranking

Request:

{
  "state": {},
  "instructions": "Choose the best candidate",
  "temperature": 1.0,
  "candidates": {
    "candidate-a": {},
    "candidate-b": {}
  }
}

Result:

{
  "model": "clm",
  "ranked": [
    {"rank": 1, "key": "candidate-a", "probability": 0.75},
    {"rank": 2, "key": "candidate-b", "probability": 0.25}
  ]
}

KEV typed decisions

Request:

{
  "state": {},
  "questions": {
    "question-id": {
      "type": "noul",
      "instructions": "Should this action be taken?",
      "criteria": {}
    }
  }
}

Result:

{
  "model": "kev",
  "answers": {
    "question-id": {
      "type": "noul",
      "noul": 0.75,
      "confidence": 0.5
    }
  }
}

The package contents select the private ranking or decision implementation. No public component-session, recursive-value, cache-control, prefix-control, benchmark, or model-specific handle families are introduced.

Runtime architecture

  • Loads and validates a component manifest once into an immutable package record.
  • Supports Mobius and Model Builder CLM/KEV component layouts.
  • Reuses existing ORT GenAI Config, Tokenizer, Tensor, NamedTensors, provider, and session-option infrastructure.
  • Runs one private ORT session per named component.
  • Applies normal thread defaults, provider options, path resolution, and custom-op registration to every component session.
  • Keeps CLM projection caches and KEV token/prefix caches private, bounded, and session-local.
  • Serializes execution and cache mutation per session.
  • Disables CUDA graph capture for component sessions until cross-component capture is qualified.

Reliability and portability

  • Enforces configured context limits without silently truncating requests.
  • Prevents fused CLM buckets from exceeding model capacity.
  • Uses stable double-precision softmax for extreme valid temperatures.
  • Preserves insertion order and integer-versus-floating-point JSON semantics.
  • Rejects duplicate keys, excessive nesting, malformed Unicode, embedded NULs, and invalid schemas.
  • Supports escaped Unicode manifest filenames and UTF-8 paths on Windows.
  • Confines component files and package-relative configuration paths.
  • Reuses the existing custom-op library allow-list and registration logic.
  • Requires ONNX Runtime 1.30 or newer for KEV packages using com.microsoft::LinearAttentionGate.

Validation

  • Full native suite: 328 tests passed
  • Focused Python suite: 8 passed, 2 opt-in integration tests skipped
  • Ruff and diff checks passed
  • Real CLM and KEV package E2E passed with ONNX Runtime 1.31
  • Shared-library inspection confirms only the three intended non-generative C exports
  • All current review threads addressed and resolved

Notes

  • Packages must contain genai_config.json.
  • component_manifest.json is authoritative when present; exact released CLM/KEV layouts remain supported for compatibility.
  • Managed-language wrappers are delivered by dependent binding changes.
  • No model weights or generated packages are included.

This comment was marked as resolved.

@apsonawane
Akshay Sonawane (apsonawane) changed the base branch from main to asonawane/non-generative-components September 29, 2026 21:52
@apsonawane
Akshay Sonawane (apsonawane) force-pushed the asonawane/non-generative-runtime branch 2 times, most recently from 2b11727 to 161fcb3 Compare September 29, 2026 22:35

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

The legacy component loader can follow symlinks outside the package root, bypassing package path containment.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (2)

Comment thread src/models/component_session.cpp Resolved

Copilot AI 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.

Copilot review overview

🔵 Needs a closer look

UTF-8 package and manifest paths are not converted safely on Windows.

Review effort: Balanced
Findings: None

Resolved since last review (1)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Use UTF-8 conversion for manifest component filenames

src/​models/​component_session.cpp:90

Manifest filenames are JSON UTF-8 strings, but this narrow std::filesystem::path construction uses the platform-native narrow encoding on Windows. A valid non-ASCII component filename can therefore fail validation even though the later fs::path(entry.filename) conversion is UTF-8-aware; use an explicit UTF-8 filesystem conversion for this safety check.

Medium severity Convert UTF-8 package paths before filesystem operations

src/​models/​non_generative_session.cpp:36

package_path is the UTF-8 path accepted by the public API, but constructing std::filesystem::path directly from its narrow string is not Unicode-safe on Windows and can make session creation fail for non-ASCII package directories. The repository’s established conversion uses the platform-correct representation (src/models/model_package.cpp:19-23); convert from UTF-8 explicitly before canonicalizing.

This comment was marked as resolved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for simplifying this. I reviewed this revision with two independent reviewers. The API is much smaller now, and the main layering concern is addressed: the new Python binding calls the public C++ wrapper, which calls the C API. It does not call private ranking, decision, or component classes. The older private ORT header include in python.cpp was already present before this PR.

Reusing Config, Tokenizer, Tensor, NamedTensors, and the existing error and string ownership rules is the right direction. We did not find a new ownership problem at the binding boundary. I do not think another broad redesign is needed.

I suggest fixing two things before merging: component sessions should honor the existing configuration rules, and CLM should honor its configured context limit. The inline comments explain both and suggest small fixes. The other comments cover API clarity, documentation/tests, and a smaller internal cleanup; those are not equivalent runtime blockers.

Header, adapter, and public-only binding-fragment compilation checks passed across the reviews. We did not run fresh native model inference for this revision, and CI was still pending. This review does not establish model accuracy, CUDA correctness, or performance.

Comment thread src/models/component_session.cpp Resolved
Comment thread src/models/non_generative_session.cpp Resolved
Comment thread src/python/py/non_generative.py Resolved
Comment thread docs/NonGenerativeRuntime.md Resolved
Comment thread src/models/component_session.cpp Resolved

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

stacked auto-merge was automatically disabled October 28, 2026 23:09

Pull Request is not mergeable

@apsonawane
Akshay Sonawane (apsonawane) merged commit 98b2046 into main Oct 29, 2026
66 checks passed
@apsonawane
Akshay Sonawane (apsonawane) deleted the asonawane/non-generative-runtime branch October 29, 2026 05:35
Akshay Sonawane (apsonawane) added a commit that referenced this pull request Oct 30, 2026
## Summary

Optimize private CLM/KEV request tokenization without expanding the
public non-generative API.

## Changes

- Add an internal `Tokenizer::EncodeBatchRows` adapter over ORT
Extensions batch tokenization.
- Preserve ragged token rows so CLM masks, positions, and context-limit
validation use each original sequence length.
- Reuse the same batch primitive in existing padded `EncodeBatch`
overloads.
- Batch CLM structured text tokenization instead of invoking the
tokenizer once per row.
- Preserve literal-tokenizer behavior and reject null batch entries
explicitly.

## API surface

No new public C, C++, or Python APIs are added. The runtime continues to
expose:

- `OgaCreateNonGenerativeSession`
- `OgaDestroyNonGenerativeSession`
- `OgaNonGenerativeSessionRunJson`
- C++ and Python `NonGenerativeSession`

Graph capture, cache controls, action sidecars, component tensor
handles, and public tokenizer APIs remain outside this change.

## Validation

- ORT GenAI native library, Python extension, and unit-test targets
build.
- Focused native non-generative tests pass.
- Python non-generative tests pass.
- Ruff and diff checks pass.
- Real CLM and KEV package E2E passes with ONNX Runtime 1.31.

## Dependency

Stacked on #2649.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants