Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
72f828a
feat(openengine): add sibling protocol server
connorcarpenter15 Jul 12, 2026
461b120
build(ucx): avoid conflicting target link features
connorcarpenter15 Jul 13, 2026
90386d6
fix(disaggregation): prepare lazy LoRA on decode
connorcarpenter15 Jul 13, 2026
cee72f7
fix(openengine): complete context handoff accounting
connorcarpenter15 Jul 13, 2026
81a106e
fix(openengine): update server protocol contract
connorcarpenter15 Jul 21, 2026
74ce7a3
test(openengine): refresh schema release fixture
connorcarpenter15 Jul 21, 2026
20270d2
build(openengine): advance pinned protocol release
connorcarpenter15 Jul 21, 2026
d57ca59
chore: merge current main
connorcarpenter15 Jul 21, 2026
c289094
refactor(openengine): remove unused RPC handlers
connorcarpenter15 Jul 22, 2026
e8720db
feat(openengine): support schema revision 3
connorcarpenter15 Jul 22, 2026
b51a218
fix(openengine): preserve routing and model identities
connorcarpenter15 Jul 22, 2026
ffc9e8f
feat(openengine): emit structured execution evidence
connorcarpenter15 Jul 22, 2026
9b8deed
fix(openengine): avoid wildcard security false positive
connorcarpenter15 Jul 22, 2026
6953e49
fix(openengine): preserve request ownership through cleanup
connorcarpenter15 Jul 22, 2026
0113875
fix(openengine): support advertised video decoding
connorcarpenter15 Jul 22, 2026
3205e68
style(openengine): format video validation
connorcarpenter15 Jul 23, 2026
2234f97
style(openengine): format video tests
connorcarpenter15 Jul 23, 2026
2766755
fix(torch): preserve attention-dp mapping for tied embeddings
connorcarpenter15 Jul 24, 2026
d0eecfb
fix(openengine): expose attention-dp ranks
connorcarpenter15 Jul 24, 2026
918e39f
fix(openengine): retain explicit attention-dp size
connorcarpenter15 Jul 24, 2026
58132ce
build(openengine): update protocol pin
connorcarpenter15 Jul 24, 2026
be2c8a5
refactor(openengine): generate bindings from schema source
connorcarpenter15 Jul 27, 2026
3a8b4f2
fix(openengine): verify generated schema identity
connorcarpenter15 Jul 27, 2026
77335ab
build(openengine): update compact schema pin
connorcarpenter15 Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions OPENENGINE_COMMIT
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d09a7313b3af2fbcd9b17aa4d31c509207ab51db
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ if(ENABLE_UCX)

target_link_libraries(${UCX_WRAPPER_TARGET}
PRIVATE $<LINK_LIBRARY:WHOLE_ARCHIVE,ucxx::ucxx>)
target_link_libraries(${UCX_WRAPPER_TARGET} PUBLIC ucxx::ucxx ucx::ucs)
# ucxx is already linked with WHOLE_ARCHIVE above. Repeating the same target
# without a feature is rejected by newer CMake versions.
target_link_libraries(${UCX_WRAPPER_TARGET} PUBLIC ucx::ucs)
target_link_libraries(${UCX_WRAPPER_TARGET} PUBLIC ${CUDA_RT_LIB})
target_link_libraries(${UCX_WRAPPER_TARGET} PUBLIC ${TORCH_LIBRARIES})
target_link_libraries(${UCX_WRAPPER_TARGET} PRIVATE ${ZMQ_LIBRARIES})
Expand Down
7 changes: 7 additions & 0 deletions requirements-openengine.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# OpenCV 4.12 requires NumPy <2.3, while TensorRT-LLM supports NumPy <2.4 and
# current serving images use 2.3. Pin the last compatible video decoder line.
numpy>=2.0.0,<2.4
opencv-python-headless==4.11.0.86
grpcio>=1.67
grpcio-tools>=1.67
protobuf>=5.27
198 changes: 198 additions & 0 deletions scripts/install_openengine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Generate and install Python bindings from the pinned OpenEngine schema."""

import argparse
import importlib.resources
import os
import re
import runpy
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path

_BSR_MODULE_PATTERN = re.compile(r"buf\.build/openengine/openengine:([0-9a-f]{32})")


def _run(*args: str, cwd: Path) -> str:
return subprocess.run(
args,
cwd=cwd,
check=True,
capture_output=True,
text=True,
).stdout.strip()


def _bsr_commit(module: str) -> str:
match = _BSR_MODULE_PATTERN.fullmatch(module)
if match is None:
raise RuntimeError(
"OpenEngine BSR module must include an exact 32-character lowercase "
"hex commit: buf.build/openengine/openengine:<commit>"
)
return match.group(1)


def _proto_root(source: Path) -> Path:
nested = source / "proto"
if (nested / "openengine" / "v1" / "openengine.proto").is_file():
return nested
return source


def _local_source(source: Path, expected: str, source_identity: str | None) -> tuple[Path, str]:
if (source / ".git").exists():
actual = _run("git", "rev-parse", "HEAD", cwd=source)
if actual != expected:
raise RuntimeError(
f"OpenEngine sibling is at {actual}, but TensorRT-LLM pins {expected}"
)
dirty = _run("git", "status", "--porcelain", "--", "proto", cwd=source)
if dirty:
raise RuntimeError("OpenEngine proto sources have uncommitted changes")
return _proto_root(source), expected
if source_identity is None:
raise RuntimeError(
"A source export without Git metadata requires --source-identity "
"with its exact 32-character BSR commit"
)
if re.fullmatch(r"[0-9a-f]{32}", source_identity) is None:
raise RuntimeError("--source-identity must be an exact 32-character BSR commit")
return _proto_root(source), source_identity


def _export_bsr(module: str, output: Path) -> tuple[Path, str]:
identity = _bsr_commit(module)
if shutil.which("buf") is None:
raise RuntimeError("buf is required to export an OpenEngine BSR module")
subprocess.run(["buf", "export", module, "--output", str(output)], check=True)
return _proto_root(output), identity


def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--sibling",
type=Path,
help="OpenEngine source checkout (default: ../openengine-trtllm)",
)
parser.add_argument(
"--verify-only",
action="store_true",
help="Verify the sibling without invoking pip",
)
parser.add_argument(
"--source-identity",
help="Exact 32-character BSR commit for an exported source tree without Git metadata",
)
parser.add_argument(
"--buf-module",
help=("Immutable BSR module input; may also be supplied through OPENENGINE_BSR_MODULE"),
)
args = parser.parse_args()

root = Path(__file__).resolve().parents[1]
expected = (root / "OPENENGINE_COMMIT").read_text(encoding="utf-8").strip()
if re.fullmatch(r"[0-9a-f]{40}", expected) is None:
raise RuntimeError("OPENENGINE_COMMIT must contain one full lowercase Git SHA")
packaged = runpy.run_path(str(root / "tensorrt_llm" / "openengine" / "_schema_pin.py"))[
"OPENENGINE_COMMIT"
]
if packaged != expected:
raise RuntimeError(
f"Packaged OpenEngine pin is {packaged}, but OPENENGINE_COMMIT contains {expected}"
)

module = args.buf_module or os.getenv("OPENENGINE_BSR_MODULE")
if module and args.sibling:
raise RuntimeError("Use exactly one of --sibling and --buf-module")

with tempfile.TemporaryDirectory(prefix="trtllm-openengine-") as temporary:
if module:
proto_root, schema_release = _export_bsr(module, Path(temporary))
else:
sibling = (args.sibling or root.parent / "openengine-trtllm").resolve()
proto_root, schema_release = _local_source(sibling, expected, args.source_identity)
proto_files = sorted((proto_root / "openengine" / "v1").glob("*.proto"))
if not proto_files:
raise RuntimeError(f"OpenEngine proto sources are missing: {proto_root}")
if not args.verify_only:
subprocess.run(
[
sys.executable,
"-m",
"pip",
"install",
"-r",
str(root / "requirements-openengine.txt"),
],
cwd=root,
check=True,
)
generated = root / "build" / "openengine-python"
shutil.rmtree(generated, ignore_errors=True)
generated.mkdir(parents=True)
grpc_tools_include = importlib.resources.files("grpc_tools").joinpath("_proto")
subprocess.run(
[
sys.executable,
"-m",
"grpc_tools.protoc",
f"-I{proto_root}",
f"-I{grpc_tools_include}",
f"--python_out={generated}",
f"--grpc_python_out={generated}",
*(str(path) for path in proto_files),
],
cwd=root,
check=True,
)
for package in (generated / "openengine", generated / "openengine" / "v1"):
package.mkdir(parents=True, exist_ok=True)
(package / "__init__.py").touch()
(generated / "openengine" / "_schema_identity.py").write_text(
'"""Generated OpenEngine schema identity."""\n\n'
f"SCHEMA_RELEASE = {schema_release!r}\n",
encoding="utf-8",
)
(generated / "pyproject.toml").write_text(
"""\
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "tensorrt-llm-openengine-bindings"
version = "0.0.0"

[tool.setuptools.packages.find]
where = ["."]
include = ["openengine*"]
""",
encoding="utf-8",
)
subprocess.run(
[
sys.executable,
"-m",
"pip",
"install",
"--no-deps",
"--no-build-isolation",
"-e",
str(generated),
],
cwd=root,
check=True,
)

print(f"Verified OpenEngine {schema_release}")
print(f"export OPENENGINE_SCHEMA_RELEASE={schema_release}")


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def has_ext_modules(self):
devel_deps, _ = parse_requirements(
Path("requirements-dev-windows.txt"
if on_windows else "requirements-dev.txt"))
openengine_deps, _ = parse_requirements(Path("requirements-openengine.txt"))
mx_deps = ["modelexpress==0.4.1"]
constraints_file = Path("constraints.txt")
if constraints_file.exists():
Expand Down Expand Up @@ -464,6 +465,7 @@ def extract_from_precompiled(precompiled_location: str, package_data: list[str],
scripts=['tensorrt_llm/llmapi/trtllm-llmapi-launch'],
extras_require={
"devel": devel_deps,
"openengine": openengine_deps,
"mx": mx_deps,
},
zip_safe=True,
Expand Down
6 changes: 6 additions & 0 deletions tensorrt_llm/_torch/models/modeling_gemma4mm.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ class Gemma4InputProcessor(BaseMultimodalInputProcessor, BaseMultimodalDummyInpu
# image, when present) can be split across chunks safely.
mm_bidirectional_blocks = False

def get_openengine_modalities(self) -> tuple[str, ...]:
return ("image", "audio")

def get_openengine_prefill_decode_modalities(self) -> tuple[str, ...]:
return ()

def __init__(
self,
model_path: str,
Expand Down
6 changes: 6 additions & 0 deletions tensorrt_llm/_torch/models/modeling_nemotron_nano.py
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,12 @@ def forward(self, multimodal_params: List[MultimodalParams]) -> List[torch.Tenso
class NanoV2VLInputProcessor(BaseMultimodalInputProcessor, BaseMultimodalDummyInputsBuilder):
supports_token_id_mm_expansion: ClassVar[bool] = True

def get_openengine_modalities(self) -> tuple[str, ...]:
return ("image", "video", "audio")

def get_openengine_prefill_decode_modalities(self) -> tuple[str, ...]:
return ()

def __init__(
self,
model_path: str,
Expand Down
33 changes: 32 additions & 1 deletion tensorrt_llm/_torch/models/modeling_phi4mm.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
ExtraProcessedInputs, MultimodalPlaceholderMetadata,
MultimodalPlaceholderPlacement, TextPrompt,
register_input_processor)
from ...inputs.registry import MultimodalLoraSpec
from ...logger import logger
from ...lora_helper import LoraConfig
from ...sampling_params import SamplingParams
Expand Down Expand Up @@ -760,6 +761,36 @@ def forward(self, multimodal_params: List[MultimodalParams],
class Phi4MMInputProcessor(BaseMultimodalInputProcessor,
BaseMultimodalDummyInputsBuilder):

def get_model_owned_lora_identities(self) -> dict[str, int]:
return {"vision-lora": 0, "speech-lora": 1}

def get_openengine_modalities(self) -> tuple[str, ...]:
return ("image", "audio")

def get_openengine_prefill_decode_modalities(self) -> tuple[str, ...]:
return ()

def get_required_lora_spec(
self, modalities: tuple[str, ...]) -> MultimodalLoraSpec | None:
requested = set(modalities)
if {"image", "audio"}.issubset(requested):
raise ValueError(
"Phi-4 multimodal requests cannot combine image and audio because they "
"require different built-in LoRA adapters")
if "image" in requested:
return MultimodalLoraSpec(
name="vision-lora",
adapter_id=0,
path=os.path.join(self._model_path, "vision-lora"),
)
if "audio" in requested:
return MultimodalLoraSpec(
name="speech-lora",
adapter_id=1,
path=os.path.join(self._model_path, "speech-lora"),
)
return None

def __init__(self,
model_path: str,
config: transformers.PretrainedConfig,
Expand Down Expand Up @@ -1092,7 +1123,7 @@ def forward(
else:
raise NotImplementedError(
"Phi-4-multimodal does not support disaggregated inference yet. Please unset "
f"the TLLM_MULTIMODAL_DISAGGREGATED environment variable, or set it to '0'."
"the TLLM_MULTIMODAL_DISAGGREGATED environment variable, or set it to '0'."
)
mm_embedding = find_input_mm_embeds(
mm_embedding, multimodal_params[:num_context_requests])
Expand Down
6 changes: 6 additions & 0 deletions tensorrt_llm/_torch/models/modeling_qwen3vl.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,12 @@ def get_preferred_media_io_kwargs(self) -> Dict[str, Dict[str, Any]]:
# the per-frame CHW-float conversion in the IO loader.
return {"video": {"format": "np"}}

def get_openengine_modalities(self) -> tuple[str, ...]:
return ("image", "video")

def get_openengine_prefill_decode_modalities(self) -> tuple[str, ...]:
return ("image", "video")

def build_disagg_prefill_multimodal_inputs(
self, inputs: TextPrompt, mm_handles: List[Dict[str, Any]]
) -> DisaggPrefillMultimodalInputs:
Expand Down
1 change: 1 addition & 0 deletions tensorrt_llm/_torch/models/modeling_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ def __init__(self, model: TModel, *, config: ModelConfig[TConfig],
vocab_size,
hidden_size,
dtype=config.pretrained_config.torch_dtype,
mapping=config.mapping,
)
else:
if (hasattr(config, 'lora_config')
Expand Down
3 changes: 2 additions & 1 deletion tensorrt_llm/_torch/pyexecutor/py_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5802,7 +5802,8 @@ def _prepare_disagg_gen_init(self, fitting_disagg_gen_init_requests):
for resource_mgr_type in (
ResourceManagerType.KV_CACHE_MANAGER,
ResourceManagerType.SPEC_RESOURCE_MANAGER,
ResourceManagerType.DRAFT_KV_CACHE_MANAGER):
ResourceManagerType.DRAFT_KV_CACHE_MANAGER,
ResourceManagerType.PEFT_CACHE_MANAGER):
if (resource_mgr_type in self.resource_manager.resource_managers
and self.resource_manager.
resource_managers[resource_mgr_type] is not None):
Expand Down
5 changes: 4 additions & 1 deletion tensorrt_llm/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1084,7 +1084,10 @@ def _stored_block_to_json(data):
KVCacheEventSerializer._unique_tokens_to_json(token)
for token in data.tokens
],
# "lora_id": data.lora_id, # TODO (shreyasm): enable serialization of lora_id
"lora_id":
getattr(data, "lora_id", None),
"lora_name":
getattr(data, "lora_name", None),
"cache_salt":
data.cache_salt,
"cache_level":
Expand Down
Loading