Files
Model-Optimizer/examples/llm_autodeploy/api_server.py
T
Wei-Ming Chen 6ae96b5162 [NVBug 5784940] Fix autodeploy example (#764)
## What does this PR do?

**Type of change:** bug fix <!-- Use one of the following: Bug fix, new
feature, new example, new tests, documentation. -->

**Overview:**  update the example with new API


## Usage
<!-- You can potentially add a usage example below. -->

```python
# Add a code snippet demonstrating how to use this
```

## Testing
<!-- Mention how have you tested your change if applicable. -->
python examples/llm_autodeploy/api_server.py --ckpt_path
TinyLlama/TinyLlama-1.1B-Chat-v1.0 --world_size 1

## Before your PR is "*Ready for review*"
<!-- If you haven't finished some of the above items you can still open
`Draft` PR. -->

- **Make sure you read and follow [Contributor
guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md)**
and your commits are signed.
- **Is this change backward compatible?**: Yes/No <!--- If No, explain
why. -->
- **Did you write any new necessary tests?**: Yes/No
- **Did you add or update any necessary documentation?**: Yes/No
- **Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?**:
Yes/No <!--- Only for new features, API changes, critical bug fixes or
bw breaking changes. -->

## Additional Information
<!-- E.g. related issue. -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* AutoDeployConfig parameter handling has been updated to improve how
parameters are prepared for language model initialization. The
configuration method now uses optimized keyword argument formatting to
ensure consistency and clarity across the deployment system.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: weimingc <17592131+meenchen@users.noreply.github.com>
2026-01-12 15:36:15 -08:00

213 lines
6.4 KiB
Python

# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import sys
import time
import uuid
import uvicorn
from fastapi import FastAPI, HTTPException
from tensorrt_llm._torch.auto_deploy import LLM, AutoDeployConfig
from tensorrt_llm.builder import BuildConfig
from tensorrt_llm.llmapi.llm import RequestOutput
from tensorrt_llm.sampling_params import SamplingParams
from tensorrt_llm.serve.openai_protocol import (
CompletionRequest,
CompletionResponse,
CompletionResponseChoice,
UsageInfo,
)
import modelopt.torch.opt as mto
# global vars
app = FastAPI()
model_runner = None
args = None
sampling_params = None
model = "autodeploy_demo"
def build_runner_from_config(args) -> LLM:
"""Builds a model runner from our config."""
mto.enable_huggingface_checkpointing()
model_kwargs = {"max_position_embeddings": args.max_seq_len, "use_cache": False}
build_config = BuildConfig(max_seq_len=args.max_seq_len, max_batch_size=args.max_batch_size)
build_config.plugin_config.tokens_per_block = args.max_seq_len
# setup AD config
ad_config = AutoDeployConfig(
model=args.ckpt_path,
compile_backend=args.compile_backend,
device=args.device,
world_size=args.world_size,
max_batch_size=args.max_batch_size,
max_seq_len=args.max_seq_len,
max_num_tokens=args.max_num_tokens,
model_kwargs=model_kwargs,
attn_backend="triton",
)
llm = LLM(**ad_config.to_llm_kwargs())
return llm
def apply_stop_tokens(text: str, stop_words: list[str] | None) -> str:
"""Truncate text at the first occurrence of any stop token."""
if not stop_words:
return text # No stop tokens provided, return as is
for stop in stop_words:
stop_idx = text.find(stop)
if stop_idx != -1:
return text[:stop_idx] # Truncate at the first stop token
return text # No stop token found, return original text
@app.post("/v1/completions", response_model=CompletionResponse)
async def create_completion(request: CompletionRequest):
"""Endpoint to handle completion requests."""
global model_runner, model, sampling_params
if model_runner is None:
raise HTTPException(status_code=500, detail="Runner is not initialized")
# Run inference using the model_runner
if isinstance(request.prompt, str):
prompts = [request.prompt] # Single string becomes a list with one element
elif isinstance(request.prompt, list):
if all(isinstance(p, str) for p in request.prompt): # List of strings
prompts = request.prompt
else:
raise HTTPException(status_code=400, detail="Invalid prompt type")
sampling_params.temperature = request.temperature
outs = model_runner.generate(prompts, sampling_params)
# formatting outputs
outputs = []
if isinstance(outs, RequestOutput):
outs = [outs]
for i, out in enumerate(outs):
outputs.append({"prompt": out.prompt, "text": out.outputs[0].text})
# Generate unique ID
unique_id = str(uuid.uuid4())
# Generate timestamp
created_timestamp = int(time.time())
# Construct response
response = CompletionResponse(
id=unique_id,
object="text_completion",
created=created_timestamp,
model=model,
choices=[
CompletionResponseChoice(
index=i,
text=apply_stop_tokens(output["text"], request.stop),
stop_reason="stop"
if any(st in output["text"] for st in request.stop or [])
else "length",
)
for i, output in enumerate(outputs)
],
usage=UsageInfo(),
)
return response
def get_server_args():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--host", type=str, default="127.0.0.1", help="Host to run the server on")
parser.add_argument("--port", type=int, default=8000, help="port number")
parser.add_argument(
"--ckpt_path",
help="Specify where the HF checkpoint path is.",
required=True,
)
parser.add_argument(
"--device",
type=str,
default="cuda",
help=("Target device to host the model."),
)
parser.add_argument(
"--backend",
type=str,
default="torch-opt",
help=("backend to compile to model."),
)
parser.add_argument(
"--world_size",
type=int,
default=0,
help=("target world size for hosting the model."),
)
parser.add_argument(
"--max_batch_size",
type=int,
default=8,
help=("max dimension for statically allocated kv cache"),
)
parser.add_argument(
"--max_seq_len",
type=int,
default=2048,
help=("max sequence length for inference/cache"),
)
parser.add_argument(
"--max_num_tokens",
type=int,
default=128,
help=("max tokens to generate."),
)
parser.add_argument(
"--top_k",
type=int,
default=200,
help=("top_k for output sampling."),
)
parser.add_argument(
"--compile_backend",
type=str,
default="torch-opt",
help=("backend to compile the torch graph."),
)
return parser.parse_args()
def run_server():
try:
global model_runner, args, sampling_params
args = get_server_args()
model_runner = build_runner_from_config(args)
sampling_params = SamplingParams(
max_tokens=args.max_num_tokens,
top_k=args.top_k,
temperature=1.0, # default value, we will take temperature from requests
)
uvicorn.run(app, host=args.host, port=args.port)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
run_server()