NAVER 是一家韩国热门搜索引擎公司,提供 Naver Place ,这是一项基于地理的服务,可提供有关韩国数百万家企业和兴趣点的详细信息。用户可以搜索不同的地点、发表评论,以及实时进行预订或下单。
NAVER Place 垂直服务基于小语言模型 (SLMs) 来提高可用性,并专门针对 Place、Map 和 Travel。本文分享了 NVIDIA 和 NAVER 如何使用 NVIDIA TensorRT-LLM 优化 SLM 推理性能,从而在 NVIDIA Triton Inference Server 上实现基于 SLM 的垂直服务。如需详细了解 NAVER 如何使用 AI,请参阅 NAVER Place AI 开发团队简介 。
适用于 NAVER Place 评测的小语言模型
与 大语言模型(LLMs) 相比,小语言模型(SLMs)是能够以更少的参数理解自然语言的 AI 模型。众所周知,当 SLMs 针对特定域任务进行适当微调时,它们可以在内存和计算能力较低的情况下正常工作。
NAVER Place 使用定制的小语言模型(SLMs)与其内部数据集结合使用,以提供摘要(根据 NAVER Place 用户留下的评论创建)或微评论,解释每个地点的特点。

使用 SLM Transformer 解码器将参观与景点匹配
NAVER Place 从其注册地点收集收据和付款记录,以显示 NAVER Map 上每个地点的访问和评论。为此,NAVER Place 提供了一个将参观与兴趣点 (POIs) 相匹配的系统。系统还会从博客文章中发现新的 POIs,或检查重复的 POIs,以确保数据完整性并提高服务质量。

采用 NVIDIA TensorRT-LLM 实现卓越的推理性能
NVIDIA TensorRT-LLM 可加速并优化 NVIDIA GPU 上 LLM 的推理性能。它支持动态批处理以最大限度地提高吞吐量,并使用自回归模型的内存优化方法 (例如分页 KV 缓存和分块上下文) 来提高内存效率。
NAVER Place 采用了 TensorRT-LLM,因为它在吞吐量、到达第一个令牌的时间 (TTFT) 和每个输出令牌的时间 (TPOT) 方面优于其他 LLM 推理解决方案。TensorRT-LLM 在各种输入长度和输出令牌场景中始终如一地提供卓越性能。
图 3 比较了使用 Qwen 模型 测量的热门替代开源 LLM 推理库和 TensorRT-LLM 在 NVIDIA A100 和 NVIDIA H100 GPU 上各种输入和输出令牌长度的吞吐量。

TensorRT-LLM 在所有解码 – 预填充轻、预填充重、解码重和解码 – 预填充重操作模式下的表现均优于替代库。其中,使用 SLM 的解码重操作模式提供了最强的性能。此外,由于 TensorRT-LLM 提供针对最新 GPU 优化的内核,因此它在 NVIDIA Hopper 架构 上实现了特别高的性能。
要了解如何使用 TensorRT-LLM 评估性能,请参阅 NVIDIA/TensorRT-LLM GitHub repo 中提供的 性能概述 。如需详细了解构建 TensorRT-LLM 引擎的基本调优技术,请参阅 调优 TensorRT-LLM 性能的最佳实践 。
推理优化:在吞吐量和延迟之间做出权衡
本节将探讨在批量大小和内存优化技术 (例如分页 KV 缓存和动态批处理) 方面平衡 LLM 推理吞吐量和延迟的策略。
批量大小
LLM 推理服务器将请求批量处理,以更大限度地提高吞吐量。但是,这会导致高延迟。这种权衡意味着,虽然较大的批量大小可以提供更高的吞吐量,但也可能会增加响应时间,因此必须在效率和用户体验之间保持谨慎的平衡 (图 4)。通过根据您的目标 TTFT 和 TPOT 调整批量大小,您可以优化系统的性能,以更好地满足您的特定服务要求。

Paged KV 缓存和 in-flight batching
TensorRT-LLM 包括默认启用的 分页 KV 缓存 选项,可提高内存效率并增加批量大小上限,以适应需要低延迟和高吞吐量的任务。此默认设置可确保模型能够平滑扩展,以处理延迟敏感的实时请求和需要更高吞吐量的批量处理场景,从而提供更灵活、更可靠的解决方案。
TensorRT-LLM 也默认启用 In-flight batching ,这可以提高吞吐量。对于大多数任务,NAVER 团队默认使用这两个选项。
一个例外是,服务需要在较小的模型和较旧的 GPU 上实现极低的延迟。NAVER Place 在特定情况下需要极低的延迟,当关闭两个选项时,性能表现更好。例如,POI 匹配是指实时处理请求,需要极低延迟的情况。由于模型相对较小,只有 1.3 亿个,并且 NVIDIA T4 GPU 架构相对较旧,因此该服务需要将批量大小设为 1 以实现最低延迟,并关闭批处理选项。
此外,使用小模型大小为 1.3 亿,批量大小为 1,与计算开销相比,分页开销更高,从而导致延迟增加和 QPS 降低。为了解决这个问题,团队采用了连续的 KV 缓存,而不是分页 KV 缓存,因为在给定条件下,内存开销不太重要。这种选择使我们能够满足 POI 匹配等用例的严格实时要求。
精度 | 分页 KV 缓存 | 缓存块 | 输入/输出 | 最大 batch_size | QPS | 延迟 (以秒为单位) |
FP16 | 打开 | 7110 | 500/5 | 1 | 6.49 | 0.154 |
FP16 | 关闭 | 7110 | 500/5 | 1 | 8.39 | 0.119 |
虽然 POI 匹配对实时服务的延迟要求极低,但它也需要高吞吐量才能进行背景匹配。因此,我们目前对每种版本使用不同的构建选项。
"build_config": {
"max_input_len": 512,
"max_output_len": 32,
"max_batch_size": 1,
"max_beam_width": 1,
"max_num_tokens": 4096,
...
"plugin_config": {
...
"paged_kv_cache": false,
...
}
}
"build_config": {
"max_input_len": 512,
"max_output_len": 32,
"max_batch_size": 8,
"max_beam_width": 1,
"max_num_tokens": 4096,
...
"plugin_config": {
...
"paged_kv_cache": true,
...
}
}
推理优化:下游缓存
本节将探讨利用缓存技术来简化下游推理任务的优化策略。我们研究了前缀和响应缓存如何帮助减少冗余计算并提高整体效率。
前缀缓存
由于下游任务生成的提示词具有通用前缀,因此计算每个请求的整个预填充会浪费资源。为了避免这种情况,TensorRT-LLM 提供了前缀缓存,这可以显著减少内存使用量和计算负载。有关更多详细信息,请参阅 如何在 NVIDIA/TensorRT-LLM GitHub repo 中启用 KV 缓存重用 。
这种方法可以显著增强 TTFT,并且对于具有长输入长度、共享系统提示和短输出长度的任务很有帮助。它特别适用于微评论,因为生成一个微评论平均需要 40 个多步骤推理,并且每个步骤共享前缀。
但是,对于涉及高度多样化系统提示的任务,前缀缓存可能无法高效运行,同时降低缓存性能并增加管理开销,因为缓存基于最近使用最少(LRU)策略。
响应缓存
响应缓存是 NVIDIA Triton Inference Server 的一项功能,有助于避免低效的冗余推理。Triton 会使用推理请求哈希来访问响应缓存,其中包括模型名称、模型版本和模型输入。响应缓存非常有效,但有意要求重新引用的情况除外,例如多项采样解码。在实时提供的 POI 匹配中,每秒发生 4 到 5 次缓存命中,这意味着计算负载减少了 17%。有关更多详细信息,请参阅 Triton Response Cache 文档 。

使用 Triton 服务 TensorRT-LLM
使用 TensorRT-LLM 构建的 SLM 引擎可在 Triton Inference Server 上提供。Triton 提供了诸如 ensemble models 和 Business Logic Scripting (BLS) 等功能,用于组成标记化、后处理或多步骤推理等工作流。例如,NAVER Place 选择使用 BLS,是因为它为特定用例提供了所需的灵活性。本节将介绍 NAVER Place 如何最大限度地提高 Triton BLS 的优势和可用性。
通过定义明确的请求/响应模式提高可用性
Triton 模型以 pb_tensor
格式交换数据。为提高通信效率和优化 LLM 推理而选择的 BLS 结构包含预处理和后处理代码,这需要将数据类型从 pb_tensor
转换为 NumPy 数组,然后再转换回 pb_tensor
。
此过程存在两个困难。首先,如果每个模型的 IO 数据未经过验证,则调试很困难,因为在运行时发现了任何无效的数据格式或缺少必填字段。其次,由于将预处理和后处理合并到 BLS 中,代码变得更加复杂,如果模型之间存在调用依赖项,则扩展和维护变得更加困难。
这些挑战强调了需要一个定义明确的请求/响应模式,以减少运行时错误并简化代码管理,尤其是在必须将多个模型链接在一起的情况下。此外,在整个工作流中保持一致的数据格式可显著缓解调试困难,并确保更流畅的集成。例如,POI 匹配会经历复杂的工作流,如图 6 所示。

为了克服这些困难,NAVER Place 团队提出了以下方法。
标准化 IO 模式管理
基于 NVIDIA 的 Python 数据类,我们使用 Pydantic 定义了 IO 模式,这使得数据验证变得更容易。这有助于确保所有 Triton 模型的请求和响应之间的结构一致性,并增强数据验证。通过在此阶段采用定义明确的模式,开发者可以尽早检测到数据问题,并在整个推理工作流中维护一致的数据结构,最终减少调试用度并提高整体可靠性。
例如,我们定义了一个名为 BlsRequest
的类,用于管理 Triton 请求的输入数据格式并执行数据验证,如以下代码示例所示:
# NOTE: Because Triton uses pb_tensor and Numpy objects,
# it is required to declaratively manage the fields that are not defined as Python default types.
# For this, we added tTe json_schema_extra field of Pydantic to explicitly manage data types.
class BlsRequest(TritonFieldModel):
name: Optional[str] = Field(None, json_schema_extra={'data_type': "TYPE_STRING"})
subname: Optional[str] = Field(None, json_schema_extra={'data_type': "TYPE_STRING"})
biznum: Optional[str] = Field(None, json_schema_extra={'data_type': "TYPE_STRING"})
address: Optional[List[str]] = Field(None, json_schema_extra={'data_type': "TYPE_STRING"})
tel: Optional[List[str]] = Field(None, json_schema_extra={'data_type': "TYPE_STRING"})
@root_validator(pre=True)
def check_all_fields_empty(cls, values):
if not any(bool(v) for v in values.values()):
raise ValidationError("All fields cannot be empty", model=cls.__name__)
按模型实现 IO 类型转换的模块化
我们为每个模型封装了 IO 数据转换过程,并为 pb_tensor
和 Pydantic 之间的转换创建了一个通用函数,使其适合基础 Triton Python 模型。这有助于以一致的方式调用模型,而无需担心内部数据转换过程。
以下代码示例是一个函数,该函数接收 Pydantic Request 对象,将其转换为 Triton pb_tensor
,然后将模型推理的结果作为 Pydantic Response 对象返回:
def _infer_model(self, request_model, response_model_cls, model_name, request_model, **infer_kwargs):
# Converts Pydantic Request to Triton pb_tensors.
pb_tensors = self.convert_pydantic_to_pb_tensors(request_model, batch_inference)
# Runs model inference.
infer_request = pb_utils.InferenceRequest(
model_name=model_name,
inputs=pb_tensors,
requested_output_names=response_model_cls.get_field_names(),
**infer_kwargs,
)
infer_response = infer_request.exec()
# Converts Triton Response(pb_tensors) to Pydantic Response.
return self.convert_pb_tensors_to_pydantic(response, response_model_cls)
以下代码示例使用 _infer_model
调用模型。只需声明 GeneratorRequest
和 GeneratorResponse
类,而忽略了复杂的数据转换或模型调用过程。
def infer_generator(self, request, text_input, max_tokens):
response_model_cls = schema.GeneratorResponse
request_model = schema.GeneratorRequest(text_input=text_input, max_tokens=max_tokens)
return self._infer_model(
request=request,
model_name="generator_bls",
request_model=request_model,
response_model_cls=response_model_cls,
)
模块化 BLS 业务逻辑并增强可测试性
NAVER 团队通过以下方式模块化了业务逻辑,并在 BLS 中预处理和后处理代码,以实现低耦合。这有助于降低代码的复杂性,并提高可测试性和可维护性。
- 模块化预处理和后处理,并引入单元测试 将模型训练、预处理和后处理代码的业务逻辑模块化,使其可重复使用。 将测试代码设计为在 Python 运行时中独立运行,即使没有 Triton 运行时,也能验证每个模型的预处理和后处理。
- 重新定义 BLS 的角色 BLS 仅负责模型调用和端到端测试。这可以确保系统保持可扩展性,即使添加了新要求,也能最大限度地减少对 BLS 代码的影响。
- CI 简介 为业务逻辑以及预处理和后处理代码创建了 CI 测试管道。这有助于快速验证模型训练过程中所做的更改,确保它们不会影响服务。将这些测试集成到 CI 管道中可实现更早的问题检测和快速解决,在不中断服务流程的情况下确保稳定的更新。
使用这种方法,我们有效地实现了增强数据验证、代码可维护性和开发生产力的目标,从而提高了基于 Triton 的 LLM 服务开发的生产力。
总结
NAVER Place 已成功使用 NVIDIA TensorRT-LLM 优化 LLM 引擎,并提高了 NVIDIA Triton Inference Server 的可用性。通过这种优化,该团队还最大限度地提高了 GPU 利用率,进一步提高了整体系统效率。整个流程有助于优化多个基于 SLM 的垂直服务,使 NAVER Place 更加用户友好。基于这些经验,我们将继续开发其他垂直模型,并将其应用于我们的服务。
开始使用 NVIDIA TensorRT-LLM 和 NVIDIA Triton Inference Server 。