inferlab-integration-tensorrt-llm 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- inferlab_integration_tensorrt_llm/__init__.py +686 -0
- inferlab_integration_tensorrt_llm/__main__.py +11 -0
- inferlab_integration_tensorrt_llm-0.2.0.dist-info/METADATA +10 -0
- inferlab_integration_tensorrt_llm-0.2.0.dist-info/RECORD +7 -0
- inferlab_integration_tensorrt_llm-0.2.0.dist-info/WHEEL +4 -0
- inferlab_integration_tensorrt_llm-0.2.0.dist-info/entry_points.txt +2 -0
- inferlab_integration_tensorrt_llm-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import os
|
|
3
|
+
import tomllib
|
|
4
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import cast
|
|
7
|
+
|
|
8
|
+
import yaml # type: ignore[import-untyped]
|
|
9
|
+
from inferlab_adapter_sdk import (
|
|
10
|
+
AdapterErrorCode,
|
|
11
|
+
AdapterOperationError,
|
|
12
|
+
EndpointProtocol,
|
|
13
|
+
EndpointRequirement,
|
|
14
|
+
IntegrationIdentity,
|
|
15
|
+
KvTransferMechanism,
|
|
16
|
+
LaunchFileDeclaration,
|
|
17
|
+
Parallelism,
|
|
18
|
+
ParallelismAttention,
|
|
19
|
+
ParallelismExperts,
|
|
20
|
+
ParallelismOuter,
|
|
21
|
+
PlanServeInput,
|
|
22
|
+
PlanServeResult,
|
|
23
|
+
ProcessSpec,
|
|
24
|
+
PublicEndpointRequirement,
|
|
25
|
+
PublicEndpointRequirementBuiltinProxy,
|
|
26
|
+
PublicEndpointRequirementReplica,
|
|
27
|
+
ReadinessProbe,
|
|
28
|
+
ReadinessProbeHttp,
|
|
29
|
+
ReadinessProbeProcessAlive,
|
|
30
|
+
RenderedServeProcess,
|
|
31
|
+
RenderInputDeclaration,
|
|
32
|
+
RenderServeInput,
|
|
33
|
+
RenderServeResult,
|
|
34
|
+
ServeProcessAllocation,
|
|
35
|
+
ServeReplicaRequirement,
|
|
36
|
+
ServeRoleInput,
|
|
37
|
+
ServeRoleKind,
|
|
38
|
+
ServeRoleLink,
|
|
39
|
+
ServeRoleLinkKvTransfer,
|
|
40
|
+
ServeRoleLinkRequestRouting,
|
|
41
|
+
ServeRoleResult,
|
|
42
|
+
ServeTopology,
|
|
43
|
+
SettingValue,
|
|
44
|
+
append_option,
|
|
45
|
+
merge_serve_args,
|
|
46
|
+
plain_setting,
|
|
47
|
+
)
|
|
48
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationError
|
|
49
|
+
|
|
50
|
+
# TensorRT-LLM declares its click options in underscore spellings plus short
|
|
51
|
+
# aliases and does no hyphen/underscore normalization, so the claim list must
|
|
52
|
+
# name every accepted spelling of every inferlab- or settings-owned option.
|
|
53
|
+
_INFERLAB_OPTION_ARITY: dict[str, int | None] = {
|
|
54
|
+
"--cluster_size": 1,
|
|
55
|
+
"--config": 1,
|
|
56
|
+
"--context_parallel_size": 1,
|
|
57
|
+
"--cp_size": 1,
|
|
58
|
+
"--enable_attention_dp": 0,
|
|
59
|
+
"--enable_chunked_prefill": 0,
|
|
60
|
+
"--ep_size": 1,
|
|
61
|
+
"--extra_llm_api_options": 1,
|
|
62
|
+
"--free_gpu_memory_fraction": 1,
|
|
63
|
+
"--host": 1,
|
|
64
|
+
"--kv_cache_dtype": 1,
|
|
65
|
+
"--kv_cache_free_gpu_memory_fraction": 1,
|
|
66
|
+
"--max_batch_size": 1,
|
|
67
|
+
"--max_num_tokens": 1,
|
|
68
|
+
"--max_seq_len": 1,
|
|
69
|
+
"--moe_cluster_parallel_size": 1,
|
|
70
|
+
"--moe_expert_parallel_size": 1,
|
|
71
|
+
"--pipeline_parallel_size": 1,
|
|
72
|
+
"--port": 1,
|
|
73
|
+
"--pp_size": 1,
|
|
74
|
+
"--served_model_name": 1,
|
|
75
|
+
"--tensor_parallel_size": 1,
|
|
76
|
+
"--tp_size": 1,
|
|
77
|
+
"--trust_remote_code": 0,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_RUNTIME_CACHE_SUBDIRS = {
|
|
81
|
+
"DG_JIT_CACHE_DIR": "deep_gemm_jit",
|
|
82
|
+
"FLASHINFER_WORKSPACE_BASE": "flashinfer",
|
|
83
|
+
"FLASHINFER_CUBIN_DIR": "flashinfer_cubin",
|
|
84
|
+
"TRITON_CACHE_DIR": "triton",
|
|
85
|
+
"TORCHINDUCTOR_CACHE_DIR": "torchinductor",
|
|
86
|
+
"TORCH_EXTENSIONS_DIR": "torch_extensions",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
_NATIVE_ROUTING_BACKEND = "trtllm-disaggregated"
|
|
90
|
+
_PREFILL_DECODE_OPTION_ARITY = {**_INFERLAB_OPTION_ARITY, "--backend": 1}
|
|
91
|
+
# Inferlab owns readiness; the router's internal guard must not expire first.
|
|
92
|
+
_ROUTER_WORKER_STARTUP_TIMEOUT_SECS = 2_147_483_647
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class TrtllmServeSettings(BaseModel):
|
|
96
|
+
model_config = ConfigDict(extra="forbid")
|
|
97
|
+
|
|
98
|
+
max_batch_size: int | None = Field(default=None, ge=1)
|
|
99
|
+
max_num_tokens: int | None = Field(default=None, ge=1)
|
|
100
|
+
max_seq_len: int | None = Field(default=None, ge=1)
|
|
101
|
+
kv_cache_dtype: str | None = None
|
|
102
|
+
free_gpu_memory_fraction: float | None = Field(default=None, gt=0.0, le=1.0)
|
|
103
|
+
enable_chunked_prefill: bool = False
|
|
104
|
+
trust_remote_code: bool = False
|
|
105
|
+
# Source YAML; P/D composition overrides its transport and cache invariants.
|
|
106
|
+
extra_llm_api_options: str | None = None
|
|
107
|
+
extra_args: list[str] | None = None
|
|
108
|
+
extra_env: dict[str, str] | None = None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _runtime_cache_env(root: str) -> dict[str, str]:
|
|
112
|
+
cache_root = Path(root)
|
|
113
|
+
return {
|
|
114
|
+
name: str(cache_root / subdirectory)
|
|
115
|
+
for name, subdirectory in _RUNTIME_CACHE_SUBDIRS.items()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _settings(values: dict[str, SettingValue]) -> TrtllmServeSettings:
|
|
120
|
+
try:
|
|
121
|
+
return TrtllmServeSettings.model_validate(
|
|
122
|
+
{key: plain_setting(value) for key, value in values.items()}
|
|
123
|
+
)
|
|
124
|
+
except ValidationError as error:
|
|
125
|
+
raise AdapterOperationError(AdapterErrorCode.invalid_settings, str(error)) from error
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _merged_settings(
|
|
129
|
+
base: dict[str, SettingValue], overrides: dict[str, SettingValue]
|
|
130
|
+
) -> TrtllmServeSettings:
|
|
131
|
+
merged = dict(base)
|
|
132
|
+
merged.update(overrides)
|
|
133
|
+
return _settings(merged)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _effective_settings(settings: TrtllmServeSettings) -> dict[str, SettingValue]:
|
|
137
|
+
return {
|
|
138
|
+
key: SettingValue(root=value)
|
|
139
|
+
for key, value in settings.model_dump(exclude_none=True).items()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _yaml_mapping(value: object, source: str) -> dict[str, object]:
|
|
144
|
+
if not isinstance(value, dict):
|
|
145
|
+
raise AdapterOperationError(
|
|
146
|
+
AdapterErrorCode.invalid_settings,
|
|
147
|
+
f"TensorRT-LLM YAML {source} must be a mapping",
|
|
148
|
+
)
|
|
149
|
+
mapping = cast(dict[object, object], value)
|
|
150
|
+
if not all(isinstance(key, str) for key in mapping):
|
|
151
|
+
raise AdapterOperationError(
|
|
152
|
+
AdapterErrorCode.invalid_settings,
|
|
153
|
+
f"TensorRT-LLM YAML {source} must use string keys",
|
|
154
|
+
)
|
|
155
|
+
return cast(dict[str, object], mapping)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _render_source_path(path: str) -> str:
|
|
159
|
+
if Path(path).is_absolute():
|
|
160
|
+
return path
|
|
161
|
+
return os.path.normpath(Path(".inferlab") / path)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _load_worker_config(input: RenderServeInput, path: str | None) -> dict[str, object]:
|
|
165
|
+
if path is None:
|
|
166
|
+
return {}
|
|
167
|
+
supplied = next(
|
|
168
|
+
(item for item in input.render_inputs if item.source_path == _render_source_path(path)),
|
|
169
|
+
None,
|
|
170
|
+
)
|
|
171
|
+
if supplied is None:
|
|
172
|
+
raise AdapterOperationError(
|
|
173
|
+
AdapterErrorCode.invalid_request,
|
|
174
|
+
f"TensorRT-LLM render input {path!r} was not supplied",
|
|
175
|
+
)
|
|
176
|
+
try:
|
|
177
|
+
value: object = yaml.safe_load(supplied.text)
|
|
178
|
+
except yaml.YAMLError as error:
|
|
179
|
+
raise AdapterOperationError(
|
|
180
|
+
AdapterErrorCode.invalid_settings,
|
|
181
|
+
f"cannot parse TensorRT-LLM extra_llm_api_options {path!r}: {error}",
|
|
182
|
+
) from error
|
|
183
|
+
if value is None:
|
|
184
|
+
return {}
|
|
185
|
+
return dict(_yaml_mapping(value, repr(path)))
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _nested_mapping(config: dict[str, object], key: str) -> dict[str, object]:
|
|
189
|
+
value = config.get(key)
|
|
190
|
+
if value is None:
|
|
191
|
+
nested: dict[str, object] = {}
|
|
192
|
+
else:
|
|
193
|
+
nested = dict(_yaml_mapping(value, key))
|
|
194
|
+
config[key] = nested
|
|
195
|
+
return nested
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _worker_launch_text(
|
|
199
|
+
input: RenderServeInput, settings: TrtllmServeSettings, kind: ServeRoleKind
|
|
200
|
+
) -> str:
|
|
201
|
+
config = _load_worker_config(input, settings.extra_llm_api_options)
|
|
202
|
+
config["backend"] = "pytorch"
|
|
203
|
+
transceiver = _nested_mapping(config, "cache_transceiver_config")
|
|
204
|
+
transceiver["backend"] = "NIXL"
|
|
205
|
+
transceiver["transceiver_runtime"] = "PYTHON"
|
|
206
|
+
kv_cache = _nested_mapping(config, "kv_cache_config")
|
|
207
|
+
kv_cache["enable_block_reuse"] = False
|
|
208
|
+
if kind == ServeRoleKind.prefill:
|
|
209
|
+
config["disable_overlap_scheduler"] = True
|
|
210
|
+
return cast(str, yaml.safe_dump(config, sort_keys=False))
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _launch_file(
|
|
214
|
+
runtime_cache_root: str, name: str, text: str
|
|
215
|
+
) -> tuple[LaunchFileDeclaration, str]:
|
|
216
|
+
digest = hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
217
|
+
relative_path = f"launch-files/{digest}/{name}"
|
|
218
|
+
declaration = LaunchFileDeclaration(
|
|
219
|
+
relative_path=relative_path,
|
|
220
|
+
sha256=digest,
|
|
221
|
+
text=text,
|
|
222
|
+
)
|
|
223
|
+
return declaration, str(Path(runtime_cache_root) / relative_path)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _adapter_version() -> str:
|
|
227
|
+
try:
|
|
228
|
+
return version("inferlab-integration-tensorrt-llm")
|
|
229
|
+
except PackageNotFoundError:
|
|
230
|
+
# Raw source-tree execution with no installed distribution
|
|
231
|
+
# metadata: the adjacent pyproject is the version authority.
|
|
232
|
+
# External-image lowering mounts each package's dist-info beside
|
|
233
|
+
# its module, so importlib.metadata resolves there instead.
|
|
234
|
+
pyproject = Path(__file__).resolve().parents[2] / "pyproject.toml"
|
|
235
|
+
with pyproject.open("rb") as handle:
|
|
236
|
+
project_version: str = tomllib.load(handle)["project"]["version"]
|
|
237
|
+
return project_version
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _identity() -> IntegrationIdentity:
|
|
241
|
+
return IntegrationIdentity(
|
|
242
|
+
adapter_id="inferlab-tensorrt-llm",
|
|
243
|
+
adapter_version=_adapter_version(),
|
|
244
|
+
framework="tensorrt-llm",
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _effective_parallelism(declared: Parallelism) -> Parallelism:
|
|
249
|
+
"""The TensorRT-LLM 1.3 algebra: `outer.tensor_parallel_size` is the
|
|
250
|
+
tensor-parallel world (`--tp_size`), which attention data parallelism
|
|
251
|
+
divides all-or-nothing (`--enable_attention_dp` replicates attention on
|
|
252
|
+
every rank) and expert parallelism divides freely (the framework derives
|
|
253
|
+
the MoE tensor split from `moe_tp * moe_ep == tp`). Context parallelism
|
|
254
|
+
multiplies the TensorRT-LLM world instead of dividing it, and MoE data
|
|
255
|
+
and dense-tensor parallelism have no TensorRT-LLM equivalent, so those
|
|
256
|
+
components reject rather than silently reshape the deployment."""
|
|
257
|
+
outer = declared.outer or ParallelismOuter()
|
|
258
|
+
attention = declared.attention or ParallelismAttention()
|
|
259
|
+
experts = declared.experts or ParallelismExperts()
|
|
260
|
+
outer_tp = outer.tensor_parallel_size or 1
|
|
261
|
+
outer_pp = outer.pipeline_parallel_size or 1
|
|
262
|
+
attention_dp = attention.data_parallel_size or 1
|
|
263
|
+
if (attention.context_parallel_size or 1) != 1:
|
|
264
|
+
raise AdapterOperationError(
|
|
265
|
+
AdapterErrorCode.invalid_settings,
|
|
266
|
+
"the TensorRT-LLM integration does not support attention context parallelism",
|
|
267
|
+
)
|
|
268
|
+
if attention_dp not in (1, outer_tp):
|
|
269
|
+
raise AdapterOperationError(
|
|
270
|
+
AdapterErrorCode.invalid_settings,
|
|
271
|
+
f"TensorRT-LLM attention data parallelism is all-or-nothing: "
|
|
272
|
+
f"attention.data_parallel_size must be 1 or equal "
|
|
273
|
+
f"outer.tensor_parallel_size ({outer_tp}), got {attention_dp}",
|
|
274
|
+
)
|
|
275
|
+
effective_attention_tp = outer_tp // attention_dp
|
|
276
|
+
if (
|
|
277
|
+
attention.tensor_parallel_size is not None
|
|
278
|
+
and attention.tensor_parallel_size != effective_attention_tp
|
|
279
|
+
):
|
|
280
|
+
raise AdapterOperationError(
|
|
281
|
+
AdapterErrorCode.invalid_settings,
|
|
282
|
+
"TensorRT-LLM effective attention.tensor_parallel_size is "
|
|
283
|
+
f"outer.tensor_parallel_size / attention.data_parallel_size "
|
|
284
|
+
f"({effective_attention_tp})",
|
|
285
|
+
)
|
|
286
|
+
if (experts.data_parallel_size or 1) != 1:
|
|
287
|
+
raise AdapterOperationError(
|
|
288
|
+
AdapterErrorCode.invalid_settings,
|
|
289
|
+
"the TensorRT-LLM integration does not support MoE data parallelism",
|
|
290
|
+
)
|
|
291
|
+
if (experts.dense_tensor_parallel_size or 1) != 1:
|
|
292
|
+
raise AdapterOperationError(
|
|
293
|
+
AdapterErrorCode.invalid_settings,
|
|
294
|
+
"the TensorRT-LLM integration does not support dense tensor parallelism",
|
|
295
|
+
)
|
|
296
|
+
expert_ep = experts.expert_parallel_size or 1
|
|
297
|
+
if outer_tp % expert_ep != 0:
|
|
298
|
+
raise AdapterOperationError(
|
|
299
|
+
AdapterErrorCode.invalid_settings,
|
|
300
|
+
f"TensorRT-LLM experts.expert_parallel_size ({expert_ep}) "
|
|
301
|
+
f"must divide outer.tensor_parallel_size ({outer_tp})",
|
|
302
|
+
)
|
|
303
|
+
effective_expert_tp = outer_tp // expert_ep
|
|
304
|
+
if (
|
|
305
|
+
experts.tensor_parallel_size is not None
|
|
306
|
+
and experts.tensor_parallel_size != effective_expert_tp
|
|
307
|
+
):
|
|
308
|
+
raise AdapterOperationError(
|
|
309
|
+
AdapterErrorCode.invalid_settings,
|
|
310
|
+
"TensorRT-LLM effective experts.tensor_parallel_size is "
|
|
311
|
+
f"outer.tensor_parallel_size / experts.expert_parallel_size "
|
|
312
|
+
f"({effective_expert_tp})",
|
|
313
|
+
)
|
|
314
|
+
return Parallelism(
|
|
315
|
+
outer=ParallelismOuter(
|
|
316
|
+
tensor_parallel_size=outer_tp,
|
|
317
|
+
pipeline_parallel_size=outer_pp,
|
|
318
|
+
),
|
|
319
|
+
attention=ParallelismAttention(
|
|
320
|
+
tensor_parallel_size=effective_attention_tp,
|
|
321
|
+
data_parallel_size=attention_dp,
|
|
322
|
+
context_parallel_size=1,
|
|
323
|
+
),
|
|
324
|
+
experts=ParallelismExperts(
|
|
325
|
+
tensor_parallel_size=effective_expert_tp,
|
|
326
|
+
data_parallel_size=1,
|
|
327
|
+
expert_parallel_size=expert_ep,
|
|
328
|
+
dense_tensor_parallel_size=1,
|
|
329
|
+
),
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _role_for_kind(input: PlanServeInput, kind: ServeRoleKind) -> ServeRoleInput:
|
|
334
|
+
matches = [role for role in input.roles if role.kind == kind]
|
|
335
|
+
if len(matches) != 1:
|
|
336
|
+
raise AdapterOperationError(
|
|
337
|
+
AdapterErrorCode.invalid_settings,
|
|
338
|
+
f"{input.topology.value} topology requires exactly one {kind.value} role",
|
|
339
|
+
)
|
|
340
|
+
return matches[0]
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _replica_id(role: ServeRoleInput, replica_index: int) -> str:
|
|
344
|
+
base = "server" if role.kind == ServeRoleKind.serve else role.id
|
|
345
|
+
if role.replica_count == 1:
|
|
346
|
+
return base
|
|
347
|
+
return f"{base}-{replica_index:03d}"
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _accelerator_count(parallelism: Parallelism) -> int:
|
|
351
|
+
outer = parallelism.outer or ParallelismOuter()
|
|
352
|
+
return (outer.tensor_parallel_size or 1) * (outer.pipeline_parallel_size or 1)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _plan_role(
|
|
356
|
+
input: PlanServeInput, role: ServeRoleInput
|
|
357
|
+
) -> tuple[ServeRoleResult, list[ServeReplicaRequirement]]:
|
|
358
|
+
if role.replica_count < 1:
|
|
359
|
+
raise AdapterOperationError(
|
|
360
|
+
AdapterErrorCode.invalid_settings,
|
|
361
|
+
f"role {role.id!r} replica count must be positive",
|
|
362
|
+
)
|
|
363
|
+
settings = _merged_settings(input.settings, role.settings)
|
|
364
|
+
parallelism = _effective_parallelism(role.parallelism)
|
|
365
|
+
replicas = [
|
|
366
|
+
ServeReplicaRequirement(
|
|
367
|
+
id=_replica_id(role, replica_index),
|
|
368
|
+
role_id=role.id,
|
|
369
|
+
replica_index=replica_index,
|
|
370
|
+
accelerator_count=_accelerator_count(parallelism),
|
|
371
|
+
ports=[],
|
|
372
|
+
primary_ports=["master"],
|
|
373
|
+
primary_readiness=ReadinessProbe(root=ReadinessProbeHttp(path="/health")),
|
|
374
|
+
worker_readiness=ReadinessProbe(root=ReadinessProbeProcessAlive()),
|
|
375
|
+
)
|
|
376
|
+
for replica_index in range(role.replica_count)
|
|
377
|
+
]
|
|
378
|
+
return (
|
|
379
|
+
ServeRoleResult(
|
|
380
|
+
id=role.id,
|
|
381
|
+
kind=role.kind,
|
|
382
|
+
replica_count=role.replica_count,
|
|
383
|
+
effective_settings=_effective_settings(settings),
|
|
384
|
+
effective_parallelism=parallelism,
|
|
385
|
+
),
|
|
386
|
+
replicas,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _endpoint_requirement() -> EndpointRequirement:
|
|
391
|
+
return EndpointRequirement(
|
|
392
|
+
protocol=EndpointProtocol(),
|
|
393
|
+
api_path="/v1/completions",
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _plan_single(input: PlanServeInput) -> PlanServeResult:
|
|
398
|
+
if input.kv_transfer is not None:
|
|
399
|
+
raise AdapterOperationError(
|
|
400
|
+
AdapterErrorCode.invalid_settings,
|
|
401
|
+
"single topology does not use a KV-transfer mechanism",
|
|
402
|
+
)
|
|
403
|
+
if input.routing_backend != "builtin":
|
|
404
|
+
raise AdapterOperationError(
|
|
405
|
+
AdapterErrorCode.invalid_settings,
|
|
406
|
+
f"the TensorRT-LLM integration does not support routing backend "
|
|
407
|
+
f"{input.routing_backend!r}",
|
|
408
|
+
)
|
|
409
|
+
role = _role_for_kind(input, ServeRoleKind.serve)
|
|
410
|
+
role_result, replicas = _plan_role(input, role)
|
|
411
|
+
return PlanServeResult(
|
|
412
|
+
integration=_identity(),
|
|
413
|
+
effective_settings=_effective_settings(_settings(input.settings)),
|
|
414
|
+
effective_parallelism=_effective_parallelism(input.parallelism),
|
|
415
|
+
roles=[role_result],
|
|
416
|
+
replicas=replicas,
|
|
417
|
+
links=[],
|
|
418
|
+
public_endpoint=PublicEndpointRequirement(
|
|
419
|
+
root=PublicEndpointRequirementReplica(replica_id=replicas[0].id)
|
|
420
|
+
),
|
|
421
|
+
endpoint=_endpoint_requirement(),
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _plan_prefill_decode(input: PlanServeInput) -> PlanServeResult:
|
|
426
|
+
if input.kv_transfer != KvTransferMechanism.nixl:
|
|
427
|
+
raise AdapterOperationError(
|
|
428
|
+
AdapterErrorCode.invalid_settings,
|
|
429
|
+
"TensorRT-LLM prefill_decode requires NIXL KV transfer",
|
|
430
|
+
)
|
|
431
|
+
if input.routing_backend not in {"builtin", _NATIVE_ROUTING_BACKEND}:
|
|
432
|
+
raise AdapterOperationError(
|
|
433
|
+
AdapterErrorCode.invalid_settings,
|
|
434
|
+
f"TensorRT-LLM does not support routing backend {input.routing_backend!r}",
|
|
435
|
+
)
|
|
436
|
+
prefill = _role_for_kind(input, ServeRoleKind.prefill)
|
|
437
|
+
decode = _role_for_kind(input, ServeRoleKind.decode)
|
|
438
|
+
prefill_result, prefill_replicas = _plan_role(input, prefill)
|
|
439
|
+
decode_result, decode_replicas = _plan_role(input, decode)
|
|
440
|
+
roles = [prefill_result, decode_result]
|
|
441
|
+
replicas = [*prefill_replicas, *decode_replicas]
|
|
442
|
+
source_paths = dict.fromkeys(
|
|
443
|
+
_render_source_path(path)
|
|
444
|
+
for role in roles
|
|
445
|
+
if (path := _settings(role.effective_settings).extra_llm_api_options) is not None
|
|
446
|
+
)
|
|
447
|
+
links = [
|
|
448
|
+
ServeRoleLink(
|
|
449
|
+
root=ServeRoleLinkRequestRouting(
|
|
450
|
+
source="router",
|
|
451
|
+
targets=[prefill.id, decode.id],
|
|
452
|
+
)
|
|
453
|
+
),
|
|
454
|
+
ServeRoleLink(
|
|
455
|
+
root=ServeRoleLinkKvTransfer(
|
|
456
|
+
source=prefill.id,
|
|
457
|
+
target=decode.id,
|
|
458
|
+
mechanism=KvTransferMechanism.nixl,
|
|
459
|
+
)
|
|
460
|
+
),
|
|
461
|
+
]
|
|
462
|
+
if input.routing_backend == "builtin":
|
|
463
|
+
public_endpoint = PublicEndpointRequirement(
|
|
464
|
+
root=PublicEndpointRequirementBuiltinProxy(
|
|
465
|
+
process_id="proxy",
|
|
466
|
+
role_id="router",
|
|
467
|
+
prefill_role=prefill.id,
|
|
468
|
+
decode_role=decode.id,
|
|
469
|
+
readiness=ReadinessProbe(root=ReadinessProbeHttp(path="/healthcheck")),
|
|
470
|
+
)
|
|
471
|
+
)
|
|
472
|
+
else:
|
|
473
|
+
roles.append(
|
|
474
|
+
ServeRoleResult(
|
|
475
|
+
id="router",
|
|
476
|
+
kind=ServeRoleKind.router,
|
|
477
|
+
replica_count=1,
|
|
478
|
+
effective_settings={},
|
|
479
|
+
effective_parallelism=Parallelism(),
|
|
480
|
+
)
|
|
481
|
+
)
|
|
482
|
+
replicas.append(
|
|
483
|
+
ServeReplicaRequirement(
|
|
484
|
+
id="router",
|
|
485
|
+
role_id="router",
|
|
486
|
+
replica_index=0,
|
|
487
|
+
accelerator_count=0,
|
|
488
|
+
ports=[],
|
|
489
|
+
primary_ports=[],
|
|
490
|
+
primary_readiness=ReadinessProbe(root=ReadinessProbeHttp(path="/health")),
|
|
491
|
+
worker_readiness=ReadinessProbe(root=ReadinessProbeProcessAlive()),
|
|
492
|
+
)
|
|
493
|
+
)
|
|
494
|
+
public_endpoint = PublicEndpointRequirement(
|
|
495
|
+
root=PublicEndpointRequirementReplica(replica_id="router")
|
|
496
|
+
)
|
|
497
|
+
return PlanServeResult(
|
|
498
|
+
integration=_identity(),
|
|
499
|
+
effective_settings=_effective_settings(_settings(input.settings)),
|
|
500
|
+
effective_parallelism=_effective_parallelism(input.parallelism),
|
|
501
|
+
roles=roles,
|
|
502
|
+
replicas=replicas,
|
|
503
|
+
links=links,
|
|
504
|
+
public_endpoint=public_endpoint,
|
|
505
|
+
endpoint=_endpoint_requirement(),
|
|
506
|
+
render_inputs=[
|
|
507
|
+
RenderInputDeclaration(source_path=source_path) for source_path in source_paths
|
|
508
|
+
],
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def plan_serve(input: PlanServeInput) -> PlanServeResult:
|
|
513
|
+
if input.profiling:
|
|
514
|
+
raise AdapterOperationError(
|
|
515
|
+
AdapterErrorCode.invalid_settings,
|
|
516
|
+
"the TensorRT-LLM integration does not support profiling capture yet",
|
|
517
|
+
)
|
|
518
|
+
if input.topology == ServeTopology.single:
|
|
519
|
+
return _plan_single(input)
|
|
520
|
+
return _plan_prefill_decode(input)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def _render_worker(
|
|
524
|
+
input: RenderServeInput,
|
|
525
|
+
role: ServeRoleResult,
|
|
526
|
+
allocation: ServeProcessAllocation,
|
|
527
|
+
) -> RenderedServeProcess:
|
|
528
|
+
settings = _settings(role.effective_settings)
|
|
529
|
+
outer = role.effective_parallelism.outer or ParallelismOuter()
|
|
530
|
+
attention = role.effective_parallelism.attention or ParallelismAttention()
|
|
531
|
+
experts = role.effective_parallelism.experts or ParallelismExperts()
|
|
532
|
+
argv = [
|
|
533
|
+
"python3",
|
|
534
|
+
"-m",
|
|
535
|
+
"tensorrt_llm.commands.serve",
|
|
536
|
+
allocation.model_locator,
|
|
537
|
+
]
|
|
538
|
+
inferlab_args = [
|
|
539
|
+
"--host",
|
|
540
|
+
allocation.endpoint.host,
|
|
541
|
+
"--port",
|
|
542
|
+
str(allocation.endpoint.port),
|
|
543
|
+
"--served_model_name",
|
|
544
|
+
input.model.served_name,
|
|
545
|
+
"--tensor_parallel_size",
|
|
546
|
+
str(outer.tensor_parallel_size or 1),
|
|
547
|
+
]
|
|
548
|
+
if (outer.pipeline_parallel_size or 1) != 1:
|
|
549
|
+
inferlab_args.extend(["--pipeline_parallel_size", str(outer.pipeline_parallel_size)])
|
|
550
|
+
if (attention.data_parallel_size or 1) != 1:
|
|
551
|
+
inferlab_args.append("--enable_attention_dp")
|
|
552
|
+
if (experts.expert_parallel_size or 1) != 1:
|
|
553
|
+
inferlab_args.extend(["--moe_expert_parallel_size", str(experts.expert_parallel_size)])
|
|
554
|
+
append_option(inferlab_args, "--max_batch_size", settings.max_batch_size)
|
|
555
|
+
append_option(inferlab_args, "--max_num_tokens", settings.max_num_tokens)
|
|
556
|
+
append_option(inferlab_args, "--max_seq_len", settings.max_seq_len)
|
|
557
|
+
append_option(inferlab_args, "--kv_cache_dtype", settings.kv_cache_dtype)
|
|
558
|
+
append_option(inferlab_args, "--free_gpu_memory_fraction", settings.free_gpu_memory_fraction)
|
|
559
|
+
launch_files: list[LaunchFileDeclaration] = []
|
|
560
|
+
if input.topology == ServeTopology.prefill_decode:
|
|
561
|
+
if role.kind not in {ServeRoleKind.prefill, ServeRoleKind.decode}:
|
|
562
|
+
raise AdapterOperationError(
|
|
563
|
+
AdapterErrorCode.invalid_request,
|
|
564
|
+
f"prefill_decode allocation has unsupported role {role.id!r}",
|
|
565
|
+
)
|
|
566
|
+
launch_text = _worker_launch_text(input, settings, role.kind)
|
|
567
|
+
launch_file, resolved_path = _launch_file(
|
|
568
|
+
allocation.runtime_cache_root,
|
|
569
|
+
"extra-llm-api-options.yaml",
|
|
570
|
+
launch_text,
|
|
571
|
+
)
|
|
572
|
+
launch_files.append(launch_file)
|
|
573
|
+
inferlab_args.extend(["--extra_llm_api_options", resolved_path])
|
|
574
|
+
inferlab_args.extend(["--backend", "pytorch"])
|
|
575
|
+
else:
|
|
576
|
+
append_option(inferlab_args, "--extra_llm_api_options", settings.extra_llm_api_options)
|
|
577
|
+
if settings.enable_chunked_prefill:
|
|
578
|
+
inferlab_args.append("--enable_chunked_prefill")
|
|
579
|
+
if settings.trust_remote_code:
|
|
580
|
+
inferlab_args.append("--trust_remote_code")
|
|
581
|
+
option_arity = (
|
|
582
|
+
_PREFILL_DECODE_OPTION_ARITY
|
|
583
|
+
if input.topology == ServeTopology.prefill_decode
|
|
584
|
+
else _INFERLAB_OPTION_ARITY
|
|
585
|
+
)
|
|
586
|
+
argv.extend(merge_serve_args(settings.extra_args or [], inferlab_args, option_arity))
|
|
587
|
+
process_env = _runtime_cache_env(allocation.runtime_cache_root)
|
|
588
|
+
process_env.update(settings.extra_env or {})
|
|
589
|
+
return RenderedServeProcess(
|
|
590
|
+
id=allocation.process_id,
|
|
591
|
+
launch_files=launch_files,
|
|
592
|
+
process=ProcessSpec(argv=argv, env=process_env),
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def _rank_zero_allocations(
|
|
597
|
+
input: RenderServeInput, kind: ServeRoleKind
|
|
598
|
+
) -> list[ServeProcessAllocation]:
|
|
599
|
+
role_ids = {role.id for role in input.roles if role.kind == kind}
|
|
600
|
+
return sorted(
|
|
601
|
+
[
|
|
602
|
+
allocation
|
|
603
|
+
for allocation in input.allocations
|
|
604
|
+
if allocation.role_id in role_ids and allocation.rank == 0
|
|
605
|
+
],
|
|
606
|
+
key=lambda allocation: (allocation.replica_index, allocation.replica_id),
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def _render_native_router(
|
|
611
|
+
input: RenderServeInput, allocation: ServeProcessAllocation
|
|
612
|
+
) -> RenderedServeProcess:
|
|
613
|
+
prefill = _rank_zero_allocations(input, ServeRoleKind.prefill)
|
|
614
|
+
decode = _rank_zero_allocations(input, ServeRoleKind.decode)
|
|
615
|
+
config = {
|
|
616
|
+
"hostname": allocation.endpoint.host,
|
|
617
|
+
"port": allocation.endpoint.port,
|
|
618
|
+
"schedule_style": "context_first",
|
|
619
|
+
"context_servers": {
|
|
620
|
+
"num_instances": len(prefill),
|
|
621
|
+
"urls": [f"{item.endpoint.host}:{item.endpoint.port}" for item in prefill],
|
|
622
|
+
"router": {"type": "round_robin"},
|
|
623
|
+
},
|
|
624
|
+
"generation_servers": {
|
|
625
|
+
"num_instances": len(decode),
|
|
626
|
+
"urls": [f"{item.endpoint.host}:{item.endpoint.port}" for item in decode],
|
|
627
|
+
"router": {"type": "round_robin"},
|
|
628
|
+
},
|
|
629
|
+
}
|
|
630
|
+
text = cast(str, yaml.safe_dump(config, sort_keys=False))
|
|
631
|
+
launch_file, resolved_path = _launch_file(
|
|
632
|
+
allocation.runtime_cache_root,
|
|
633
|
+
"disaggregated.yaml",
|
|
634
|
+
text,
|
|
635
|
+
)
|
|
636
|
+
process_env = _runtime_cache_env(allocation.runtime_cache_root)
|
|
637
|
+
process_env.update(_settings(input.settings).extra_env or {})
|
|
638
|
+
return RenderedServeProcess(
|
|
639
|
+
id=allocation.process_id,
|
|
640
|
+
launch_files=[launch_file],
|
|
641
|
+
process=ProcessSpec(
|
|
642
|
+
argv=[
|
|
643
|
+
"python3",
|
|
644
|
+
"-m",
|
|
645
|
+
"tensorrt_llm.commands.serve",
|
|
646
|
+
"disaggregated",
|
|
647
|
+
"--config",
|
|
648
|
+
resolved_path,
|
|
649
|
+
"--server_start_timeout",
|
|
650
|
+
str(_ROUTER_WORKER_STARTUP_TIMEOUT_SECS),
|
|
651
|
+
],
|
|
652
|
+
env=process_env,
|
|
653
|
+
),
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def render_serve(input: RenderServeInput) -> RenderServeResult:
|
|
658
|
+
if not input.allocations:
|
|
659
|
+
raise AdapterOperationError(
|
|
660
|
+
AdapterErrorCode.invalid_request, "serve allocation must not be empty"
|
|
661
|
+
)
|
|
662
|
+
roles = {role.id: role for role in input.roles}
|
|
663
|
+
replica_counts: dict[str, int] = {}
|
|
664
|
+
for allocation in input.allocations:
|
|
665
|
+
replica_counts[allocation.replica_id] = replica_counts.get(allocation.replica_id, 0) + 1
|
|
666
|
+
processes: list[RenderedServeProcess] = []
|
|
667
|
+
for allocation in input.allocations:
|
|
668
|
+
role = roles.get(allocation.role_id)
|
|
669
|
+
if role is None:
|
|
670
|
+
raise AdapterOperationError(
|
|
671
|
+
AdapterErrorCode.invalid_request,
|
|
672
|
+
f"allocation references unknown role {allocation.role_id!r}",
|
|
673
|
+
)
|
|
674
|
+
if role.kind == ServeRoleKind.router:
|
|
675
|
+
processes.append(_render_native_router(input, allocation))
|
|
676
|
+
continue
|
|
677
|
+
if replica_counts[allocation.replica_id] > 1:
|
|
678
|
+
raise AdapterOperationError(
|
|
679
|
+
AdapterErrorCode.invalid_request,
|
|
680
|
+
"the TensorRT-LLM integration does not support multi-node serving yet",
|
|
681
|
+
)
|
|
682
|
+
processes.append(_render_worker(input, role, allocation))
|
|
683
|
+
return RenderServeResult(integration=_identity(), processes=processes)
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
__all__ = ["TrtllmServeSettings", "plan_serve", "render_serve"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: inferlab-integration-tensorrt-llm
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Inferlab adapter integration for TensorRT-LLM
|
|
5
|
+
Project-URL: Repository, https://github.com/Infer-Lab/inferlab
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Requires-Dist: inferlab-adapter-sdk==0.2.0
|
|
10
|
+
Requires-Dist: pyyaml<7,>=6
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
inferlab_integration_tensorrt_llm/__init__.py,sha256=k2uDaWNxdj56nOe48jDoljbUHdqiVUjsPmGmg0ABhfc,25598
|
|
2
|
+
inferlab_integration_tensorrt_llm/__main__.py,sha256=e1uoS23_cXd5sMrRK_nSuUPgdz37Tx1xBhxqMlCYhBg,220
|
|
3
|
+
inferlab_integration_tensorrt_llm-0.2.0.dist-info/METADATA,sha256=pQKLYf4Xf8YfErWBXDFPaf2eRlC-QkTWI5Q0aLiJ-vA,336
|
|
4
|
+
inferlab_integration_tensorrt_llm-0.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
5
|
+
inferlab_integration_tensorrt_llm-0.2.0.dist-info/entry_points.txt,sha256=Fs0UfQX0NqdHcivh5ncEDoDs8GRvLakGo1zF1MrwwOM,98
|
|
6
|
+
inferlab_integration_tensorrt_llm-0.2.0.dist-info/licenses/LICENSE,sha256=_sFBKPJJjTLuE3lL3Ir9HDHTTrB9zsZtMBAOvBfBWA4,1065
|
|
7
|
+
inferlab_integration_tensorrt_llm-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zihua Wu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|