【TVM 教程】优化大语言模型

MLC-LLM vs Llama:本地化部署大语言模型的技术对比与实战指南 本文对比了MLC-LLM和Llama在本地化部署大语言模型(LLM)方面的技术差异与实战应用。详细解析了两者的架构特点、量化技术及跨平台支持,提供了在不同硬件上的性能测试数据和优化建议,帮助开发者根据隐私保护、计算效率和能耗控制等需求选择最佳方案。 阅读详情

TVM 现已更新到 0.21.0 版本,TVM 中文文档已经和新版本对齐。

Apache TVM 是一个深度的深度学习编译框架,适用于 CPU、GPU 和各种机器学习加速芯片。更多 TVM 中文文档可访问 →Apache TVM

Apache TVM 的一个主要设计目标是便于自定义优化流程,无论是用于科研探索还是工程开发,都可以灵活迭代优化过程。本教程将涵盖以下内容:

随着大语言模型(LLM)在多个领域成为热门的研究课题,如何将它们部署在云端或边缘设备上成为一项具有挑战性的任务。本教程将演示如何使用 Apache TVM 对大语言模型进行优化。我们将使用来自 Hugging Face 的预训练 TinyLlama 模型,并将其部署到多种设备上。

总览整体流程​

在这里插入图片描述

整体流程包括以下几个步骤:

  • 构建或导入模型:可以手动构建一个神经网络模型,或从其他框架(如 PyTorch、ONNX)中导入一个预训练模型,并生成 TVM 的 IRModule。该模块包含编译所需的所有信息,包括用于表示计算图的高层 Relax 函数,以及用于描述张量程序的低层 TensorIR 函数
  • 执行可组合优化:执行一系列优化转换,包括计算图优化、张量程序优化和算子调度/分发等
  • 构建并进行通用部署:将优化后的模型构建为可部署模块,使用 TVM 通用运行时在不同设备上运行,例如 CPU、GPU 或其他加速器

构建模型结构​

我们将使用来自 Hugging Face 的预训练 TinyLlama 模型。但通常我们只会从 Hugging Face 加载预训练权重,而不是模型结构。因此,我们需要自己构建模型结构。Apache TVM 提供了类似 PyTorch 的 API 来构建模型结构,我们可以使用这些 API 来完成模型搭建。

import dataclasses
import enum
import os
from pathlib import Path
from pprint import pprint
from typing import List, Optional

import tvm
from tvm import dlight, relax, te, tir
from tvm.relax import register_pipeline
from tvm.relax.frontend import nn
from tvm.relax.frontend.nn import Tensor, op
from tvm.relax.frontend.nn.llm.kv_cache import PagedKVCache, TIRPagedKVCache
from tvm.runtime import ShapeTuple

首先,我们需要定义模型的配置。配置中包括模型的关键参数,如隐藏层维度、中间层维度等。为了方便起见,我们为 TinyLlama 模型专门定义一个常量配置。

@dataclasses.dataclass
class LlamaConfig:
    hidden_size: int = 2048
    intermediate_size: int = 5632
    num_attention_heads: int = 32
    num_hidden_layers: int = 22
    rms_norm_eps: float = 1e-05
    vocab_size: int = 32000
    rope_theta: int = 10000
    context_window_size: int = 2048
    prefill_chunk_size: int = 2048
    num_key_value_heads: int = 4
    head_dim: int = 64  # hidden_size // num_attention_heads


dev = tvm.device("cuda", 0)
target = tvm.target.Target.from_device(dev)

接着,我们定义分页 KV 缓存(Paged KV cache)的 RoPE 模式。RoPE(相对位置编码)用于在自注意力中对 query 和 key 张量进行编码。RoPE 模式可设置为 NONE、NORMAL 或 INLINE 如果 RoPE 模式为 ,则键值缓存(KV cache)不会对查询(query)和键(key)张量应用 RoPE;如果 RoPE 模式为,则在将键张量加入缓存之前,会对其应用 RoPE;如果 RoPE 模式为 INLINE,则会在注意力机制的核心计算过程中即时对查询和键张量应用 RoPE。

class RopeMode(enum.IntEnum):
    """
    分页 KV 缓存的 RoPE 模式。
    如果是 NONE,KV 缓存将不会对 q 和 k 应用 RoPE。
    如果是 NORMAL,RoPE 将在将 k 添加到缓存之前应用于 k。
    否则,RoPE 将在注意力内核中即时应用于 q/k。
    """

    NONE = 0
    NORMAL = 1
    INLINE = 2

接下来,我们定义模型的结构。模型结构包含三个部分:

  • 嵌入层(Embedding Layer) :将输入的 token ID 转换为隐藏状态
  • 解码器层(Decoder Layers) :模型的核心部分。每一层解码器包含一个自注意力层和一个前馈神经网络(FFN)层。
  • 输出层(Output Layer) :将隐藏状态转换为 logits 输出

首先我们定义 FFN 层。注意以下是一个经过优化的 FFN 实现,它将 gate 和 up 投影融合成一个 kernel。普通的 FFN 计算公式为:FFN(x) = down_proj(silu(gate(x)) * up(x)),为了更高性能,我们将 gate 和 up 投影合并优化处理。

concat_x = gate_up(x)
gate_x, up_x = split(concat_x, 2, axis=-1)
FFN(x) = down_proj(silu(gate_x) * up_x)
class LlamaFFN(nn.Module):
    def __init__(self, config: LlamaConfig):
        super().__init__()
        self.gate_up_proj = nn.Linear(
            in_features=config.hidden_size,
            out_features=2 * config.intermediate_size,
            bias=False,
        )
        self.down_proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)

    def forward(self, x: Tensor):
        concat_x1_x2 = self.gate_up_proj(x)
        x1, x2 = op.split(concat_x1_x2, 2, axis=-1)
        return self.down_proj(op.silu(x1) * x2)

接着我们定义自注意力层(Self-Attention Layer),它由以下 3 部分组成:

  • QKV 投影:将输入隐藏状态转换为 query、key、value 张量。
  • 注意力机制:计算注意力分数并执行 softmax 操作。
  • 输出投影:将注意力输出转换为隐藏状态。

在自注意力层中,我们对不同部分进行了优化:

  • QKV 投影:对 Q、K、V 执行横向融合,将它们合并为一个 kernel
  • 注意力机制:将 QKV 投影与注意力逻辑融合,进一步提升性能
class LlamaAttention(nn.Module):  # 关闭 Pylint 对“类的实例属性过多”的检查
    def __init__(self, config: LlamaConfig):
        self.head_dim = config.head_dim
        self.num_q_heads = config.num_attention_heads
        self.num_kv_heads = config.num_key_value_heads
        # QKV 投影的横向融合
        self.qkv_proj = nn.Linear(
            in_features=config.hidden_size,
            out_features=(self.num_q_heads + 2 * self.num_kv_heads) * self.head_dim,
            bias=False,
        )
        self.o_proj = nn.Linear(self.num_q_heads * self.head_dim, config.hidden_size, bias=False)

    def forward(self, hidden_states: Tensor, paged_kv_cache: PagedKVCache, layer_id: int):
        d, h_q, h_kv = self.head_dim, self.num_q_heads, self.num_kv_heads
        b, s, _ = hidden_states.shape
        # QKV 投影
        qkv = self.qkv_proj(hidden_states)
        qkv = op.reshape(qkv, (b, s, h_q + h_kv + h_kv, d))
        # 注意力计算
        output = op.reshape(
            paged_kv_cache.attention_with_fused_qkv(
                layer_id, qkv, self.num_q_heads, sm_scale=self.head_dim**-0.5
            ),
            (b, s, h_q * d),
        )
        # 输出投影
        return self.o_proj(output)

最后,我们将 FFN 层和自注意力层组合,定义完整的模型结构。

class LlamaDecoderLayer(nn.Module):
    def __init__(self, config: LlamaConfig):
        rms_norm_eps = config.rms_norm_eps
        self.self_attn = LlamaAttention(config)
        self.mlp = LlamaFFN(config)
        self.input_layernorm = nn.RMSNorm(config.hidden_size, -1, rms_norm_eps, bias=False)
        self.post_attention_layernorm = nn.RMSNorm(config.hidden_size, -1, rms_norm_eps, bias=False)

    def forward(self, hidden_states: Tensor, paged_kv_cache: PagedKVCache, layer_id: int):
        hidden_states += self.self_attn(
            self.input_layernorm(hidden_states), paged_kv_cache, layer_id
        )
        hidden_states += self.mlp(self.post_attention_layernorm(hidden_states))
        return hidden_states


class LlamaModel(nn.Module):
    def __init__(self, config: LlamaConfig):
        assert config.hidden_size % config.num_attention_heads == 0
        self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size)
        self.layers = nn.ModuleList(
            [LlamaDecoderLayer(config) for _ in range(config.num_hidden_layers)]
        )
        self.norm = nn.RMSNorm(config.hidden_size, -1, config.rms_norm_eps, bias=False)

    def forward(self, input_embed: Tensor, paged_kv_cache: PagedKVCache):
        hidden_states = input_embed
        for layer_id, layer in enumerate(self.layers):
            hidden_states = layer(hidden_states, paged_kv_cache, layer_id)
        hidden_states = self.norm(hidden_states)
        return hidden_states


class LlamaForCasualLM(nn.Module):
    def __init__(self, config: LlamaConfig):
        self.model = LlamaModel(config)
        self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
        self.num_hidden_layers = config.num_hidden_layers
        self.num_attention_heads = config.num_attention_heads
        self.num_key_value_heads = config.num_key_value_heads
        self.head_dim = config.head_dim
        self.hidden_size = config.hidden_size
        self.vocab_size = config.vocab_size
        self.rope_theta = config.rope_theta
        self.dtype = "float32"

    def to(self, dtype: Optional[str] = None):
        super().to(dtype=dtype)
        if dtype is not None:
            self.dtype = dtype

    def embed(self, input_ids: Tensor):
        return self.model.embed_tokens(input_ids)

    def get_logits(self, hidden_states: Tensor):
        logits = self.lm_head(hidden_states)
        if logits.dtype != "float32":
            logits = logits.astype("float32")
        return logits

    def prefill(self, input_embed: Tensor, paged_kv_cache: PagedKVCache):
        def _index(x: te.Tensor):  # x[:-1,:]
            b, s, d = x.shape
            return te.compute((b, 1, d), lambda i, _, k: x[i, s - 1, k], name="index")

        hidden_states = self.model(input_embed, paged_kv_cache)
        hidden_states = op.tensor_expr_op(_index, name_hint="index", args=[hidden_states])
        logits = self.get_logits(hidden_states)
        return logits, paged_kv_cache

    def decode(self, input_embed: Tensor, paged_kv_cache: PagedKVCache):
        hidden_states = self.model(input_embed, paged_kv_cache)
        logits = self.get_logits(hidden_states)
        return logits, paged_kv_cache

    def create_tir_paged_kv_cache(
        self,
        max_batch_size: tir.Var,
        max_total_seq_len: tir.Var,
        prefill_chunk_size: tir.Var,
        page_size: tir.Var,
    ) -> PagedKVCache:
        return TIRPagedKVCache(
            attn_kind="mha",
            max_batch_size=max_batch_size,
            max_total_seq_len=max_total_seq_len,
            prefill_chunk_size=prefill_chunk_size,
            page_size=page_size,
            support_sliding_window=0,
            layer_partition=relax.ShapeExpr([0, self.num_hidden_layers]),
            num_hidden_layers=self.num_hidden_layers,
            num_attention_heads=self.num_attention_heads,
            num_key_value_heads=self.num_key_value_heads,
            qk_head_dim=self.head_dim,
            v_head_dim=self.head_dim,
            mla_original_qk_head_dim=0,
            mla_original_v_head_dim=0,
            rope_mode=RopeMode.NORMAL,
            rope_scale=1,
            rope_theta=self.rope_theta,
            rope_scaling={},
            rope_ext_factors=relax.PrimValue(0),
            rotary_dim=self.head_dim,
            dtype=self.dtype,
            target=target,
            enable_disaggregation=False,
        )

    def get_default_spec(self):
        mod_spec = {
            "embed": {
                "input_ids": nn.spec.Tensor(["seq_len"], "int32"),
                "$": {
                    "param_mode": "packed",
                    "effect_mode": "none",
                },
            },
            "prefill": {
                "input_embed": nn.spec.Tensor([1, "seq_len", self.hidden_size], self.dtype),
                "paged_kv_cache": nn.spec.Object(object_type=PagedKVCache),
                "$": {
                    "param_mode": "packed",
                    "effect_mode": "none",
                },
            },
            "decode": {
                "input_embed": nn.spec.Tensor([1, 1, self.hidden_size], self.dtype),
                "paged_kv_cache": nn.spec.Object(object_type=PagedKVCache),
                "$": {
                    "param_mode": "packed",
                    "effect_mode": "none",
                },
            },
            "create_tir_paged_kv_cache": {
                "max_batch_size": int,
                "max_total_seq_len": int,
                "prefill_chunk_size": int,
                "page_size": int,
                "$": {
                    "param_mode": "none",
                    "effect_mode": "none",
                },
            },
        }
        return nn.spec.ModuleSpec.from_raw(mod_spec, self)

将模型导出为 Relax IRModule​

在定义好模型架构之后,我们可以将模型导出为 Relax IRModule。为了演示,这里只展示模型架构的一部分以及参数。

model_config = LlamaConfig()
model = LlamaForCasualLM(model_config)
model.to("float16")
mod, named_params = model.export_tvm(spec=model.get_default_spec())
prefill_str = mod["prefill"].script()
print(*prefill_str.split("\n")[3:20], sep="\n")  # 仅展示前 10 行用于演示
print("        ...")

print("\nParameters:")
pprint(named_params[:5])  # 仅展示前 5 个参数用于演示

输出

@R.function
def prefill(input_embed: R.Tensor((1, "seq_len", 2048), dtype="float16"), paged_kv_cache: R.Object, packed_params: R.Tuple(R.Tensor((32000, 2048), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2560, 2048), dtype="float16"), R.Tensor((2048, 2048), dtype="float16"), R.Tensor((11264, 2048), dtype="float16"), R.Tensor((2048, 5632), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((2048,), dtype="float16"), R.Tensor((32000, 2048), dtype="float16"))) -> R.Tuple(R.Tensor((1, 1, 32000), dtype="float32"), R.Object):
    seq_len = T.int64()
    R.func_attr({"num_input": 2})
    with R.dataflow():
        model_embed_tokens_weight1: R.Tensor((32000, 2048), dtype="float16") = packed_params[0]
        model_layers_0_self_attn_qkv_proj_weight1: R.Tensor((2560, 2048), dtype="float16") = packed_params[1]
        model_layers_0_self_attn_o_proj_weight1: R.Tensor((2048, 2048), dtype="float16") = packed_params[2]
        model_layers_0_mlp_gate_up_proj_weight1: R.Tensor((11264, 2048), dtype="float16") = packed_params[3]
        model_layers_0_mlp_down_proj_weight1: R.Tensor((2048, 5632), dtype="float16") = packed_params[4]
        model_layers_0_input_layernorm_weight1: R.Tensor((2048,), dtype="float16") = packed_params[5]
        model_layers_0_post_attention_layernorm_weight1: R.Tensor((2048,), dtype="float16") = packed_params[6]
        model_layers_1_self_attn_qkv_proj_weight1: R.Tensor((2560, 2048), dtype="float16") = packed_params[7]
        model_layers_1_self_attn_o_proj_weight1: R.Tensor((2048, 2048), dtype="float16") = packed_params[8]
        model_layers_1_mlp_gate_up_proj_weight1: R.Tensor((11264, 2048), dtype="float16") = packed_params[9]
        model_layers_1_mlp_down_proj_weight1: R.Tensor((2048, 5632), dtype="float16") = packed_params[10]
        model_layers_1_input_layernorm_weight1: R.Tensor((2048,), dtype="float16") = packed_params[11]
        ...

Parameters:
[('model.embed_tokens.weight', Tensor([32000, 2048], "float16")),
 ('model.layers.0.self_attn.qkv_proj.weight', Tensor([2560, 2048], "float16")),
 ('model.layers.0.self_attn.o_proj.weight', Tensor([2048, 2048], "float16")),
 ('model.layers.0.mlp.gate_up_proj.weight', Tensor([11264, 2048], "float16")),
 ('model.layers.0.mlp.down_proj.weight', Tensor([2048, 5632], "float16"))]

定义优化流程(Optimization Pipeline)​

我们定义了一系列用于优化模型的传递过程(pass)。这个优化流程是专门为大语言模型(LLMs)设计的。

@register_pipeline("opt_llm")
def _pipeline(  # pylint: disable=too-many-arguments
    ext_mods: List[nn.ExternModule] = None,
):
    ext_mods = ext_mods or []

    @tvm.transform.module_pass(opt_level=0)
    def _pipeline(mod: tvm.ir.IRModule, _ctx: tvm.transform.PassContext) -> tvm.ir.IRModule:
        seq = tvm.transform.Sequential(
            [
                # 第一阶段:针对高层操作图的优化
                # 可以启用 cublas以进一步优化
                relax.transform.FuseTransposeMatmul(),
                # 第二阶段:向 TIR(张量中间表示)下沉,继承 TVM Relax 的官方 "zero" 流程
                relax.transform.LegalizeOps(),
                relax.transform.AnnotateTIROpPattern(),
                relax.transform.FoldConstant(),
                relax.transform.FuseOps(),
                relax.transform.FuseTIR(),
                # 第三阶段:对 TIR 进行优化
                relax.transform.DeadCodeElimination(),
                # 第四阶段:底层优化
                dlight.ApplyDefaultSchedule(
                    dlight.gpu.Matmul(),
                    dlight.gpu.GEMV(),
                    dlight.gpu.Reduction(),
                    dlight.gpu.GeneralReduction(),
                    dlight.gpu.Fallback(),
                ),
                # 第五阶段:转换为虚拟机字节码
                relax.transform.RewriteDataflowReshape(),
                relax.transform.ToNonDataflow(),
                relax.transform.RemovePurityChecking(),
                relax.transform.CallTIRRewrite(),
                relax.transform.StaticPlanBlockMemory(),
                relax.transform.RewriteCUDAGraph(),
                relax.transform.LowerAllocTensor(),
                relax.transform.KillAfterLastUse(),
                relax.transform.LowerRuntimeBuiltin(),
                relax.transform.VMShapeLower(),
                relax.transform.AttachGlobalSymbol(),
                relax.transform.AttachExternModules(ext_mods),
            ]
        )
        mod = seq(mod)
        return mod

    return _pipeline


with target:
    ex = tvm.compile(mod, target, relax_pipeline=relax.get_pipeline("opt_llm"))
    vm = relax.VirtualMachine(ex, dev)

准备模型权重​

我们从 Hugging Face 加载预训练权重并准备模型权重。预训练权重是以 Hugging Face 格式存储的,我们需要加载权重并准备模型参数。

备注
注意:本教程中不会执行以下代码,因为在 CI 环境中无法获取预训练权重。

IS_IN_CI = os.getenv("CI", "") == "true"

HF_WEIGHT_PATH = None
# HF_WEIGHT_PATH = Path("/path/to/TinyLlama-1.1B-Chat-v1.0/")

if not IS_IN_CI:
    import numpy as np
    import safetensors.torch
    import torch

    if HF_WEIGHT_PATH is None or not HF_WEIGHT_PATH.exists():
        raise ValueError("Please set the HF_WEIGHT_PATH to the path of the pre-trained weights.")

    # Torch 格式权重
    param_dict = safetensors.torch.load_file(HF_WEIGHT_PATH / "model.safetensors", device="cpu")
    # Numpy 格式权重
    param_dict = {
        k: v.half().numpy() if v.dtype == torch.bfloat16 else v.numpy()
        for k, v in param_dict.items()
    }

    named_params = dict(named_params)
    for i in range(model_config.num_hidden_layers):
        # 在自注意力机制中添加 QKV 权重
        attn = f"model.layers.{i}.self_attn"
        param_dict[f"{attn}.qkv_proj.weight"] = np.concatenate(
            [
                param_dict.pop(f"{attn}.q_proj.weight"),  # 移除旧参数来节省内存
                param_dict.pop(f"{attn}.k_proj.weight"),
                param_dict.pop(f"{attn}.v_proj.weight"),
            ],
            axis=0,
        )
        # 在 MLP 中添加 gate 权重
        mlp = f"model.layers.{i}.mlp"
        param_dict[f"{mlp}.gate_up_proj.weight"] = np.concatenate(
            [
                param_dict.pop(f"{mlp}.gate_proj.weight"),
                param_dict.pop(f"{mlp}.up_proj.weight"),
            ],
            axis=0,
        )

    # 将参数转换为 ndarray
    params = [
        tvm.runtime.tensor(param_dict[k].astype("float16"), device=dev) for k in named_params.keys()
    ]

部署已编译模型​

当模型和权重准备好后,我们就可以在目标设备上部署已编译的模型。语言模型推理包括两个步骤:prefill 和 decode。prefill 步骤用于处理输入的 token 并存储 KVCache;decode 步骤用于生成 token,直到生成结束 token 为止。

分词​

第一步是对输入提示进行分词,并将分词后的 token 嵌入到隐藏状态中。分词和嵌入与原始模型相同。我们使用 Hugging Face 的分词器对输入提示进行分词,并将 token 嵌入到隐藏状态中。请注意,不同模型需要不同的分词方法和提示格式,具体请参考模型的文档以获取正确的分词和提示格式。

if not IS_IN_CI:
    from transformers import AutoTokenizer

    tokenizer = AutoTokenizer.from_pretrained(HF_WEIGHT_PATH)
    messages = [
        {"role": "user", "content": "What's your name?"},
    ]
    prompt = tokenizer.apply_chat_template(messages)
    input_len = len(prompt)

    # 将提示 token 加载到目标设备上的 TVM ndarray
    tokens = tvm.runtime.tensor(np.array(prompt).astype("int32"), device=dev)

创建 KVCache​

在开始推理之前,需要创建 KVCache。KVCache 用于存储注意力层的 key 和 value 张量。Apache TVM 提供了 PagedKVCache 用于存储 key 和 value 张量。我们根据指定参数创建 PagedKVCache。

if not IS_IN_CI:
    kv_cache = vm["create_tir_paged_kv_cache"](
        ShapeTuple([1]),  # max_batch_size=1
        ShapeTuple([2048]),  # max_total_seq_len=2048
        ShapeTuple([2048]),  # prefill_chunk_size=2048
        ShapeTuple([16]),  # page_size=16
    )

嵌入​

下一步是将 token 嵌入到隐藏状态中。我们使用在 Relax IRModule 中编译的 embed 函数,将 token 嵌入到隐藏状态。

nd_view_func = tvm.get_global_func("vm.builtin.reshape")


def embed(tokens, params):
    _embed = vm["embed"](tokens, params)
    # 将 hidden 的形状从 [seq_len, hidden_size] 重塑为 [1, seq_len, hidden_size]
    _embed = nd_view_func(_embed, ShapeTuple([1, _embed.shape[0], _embed.shape[1]]))
    return _embed

Prefill​

在执行前向计算之前,我们先获取一些辅助函数以做准备。

add_sequence_func = tvm.get_global_func("vm.builtin.kv_state_add_sequence")
begin_forward_func = tvm.get_global_func("vm.builtin.kv_state_begin_forward")
end_forward_func = tvm.get_global_func("vm.builtin.kv_state_end_forward")

在我们创建一个新序列时,需要调用 add_sequence_func 来初始化请求。此外,还需要调用 begin_forward_func 来开始前向计算,调用 end_forward_func 来结束前向计算。

if not IS_IN_CI:
    seq_id = 0
    add_sequence_func(kv_cache, seq_id)
    hidden_states = embed(tokens, params)
    begin_forward_func(kv_cache, ShapeTuple([seq_id]), ShapeTuple([input_len]))
    logits, kv_cache = vm["prefill"](hidden_states, kv_cache, params)
    end_forward_func(kv_cache)

现在我们从 prefill 步骤得到了输出的 logits,logits 用于通过采样生成 token。我们从 logits 中采样 token。

在本教程中,我们简化采样过程,选择概率最高的 token。实际应用中,应该基于概率分布进行采样。为了使教程简洁,采样过程在 CPU 上执行。

def sample_token(logits):
    logits_np = logits.numpy()
    return np.argmax(logits_np)


if not IS_IN_CI:
    last_token = sample_token(logits)
    output_tokens = [last_token]

Decode​

Prefill 步骤完成后,我们可以开始 decode 步骤。decode 步骤用于持续生成 token,直到生成结束 token。我们使用 Relax IRModule 中编译的 decode 函数来生成 token。可右键另存为下载。

if not IS_IN_CI:
    print("The generated token:")

    while last_token != tokenizer.eos_token_id:
        tokens = tvm.runtime.tensor(np.array([last_token]).astype("int32"), device=dev)
        hidden_states = embed(tokens, params)
        begin_forward_func(kv_cache, ShapeTuple([seq_id]), ShapeTuple([1]))
        logits, kv_cache = vm["decode"](hidden_states, kv_cache, params)

        end_forward_func(kv_cache)
        last_token = sample_token(logits)
        output_tokens.append(last_token)

    print(tokenizer.decode(output_tokens))
大语言模型LLM编译优化:LLVM与TVM(LLM系列20) 一系列实验结果表明,经过LLVM与TVM联合优化的大规模语言模型在多个基准测试中均表现出显著优于原始模型的性能表现,这有力验证了二者在模型编译优化方面的巨大潜力。尤为值得注意的是,TVM巧妙地整合了LLVM作为其后端编译器,将优化后的计算图映射成LLVM IR,进而利用LLVM强大的底层优化能力生成高度优化的机器代码。未来,我们期待LLVM与TVM能在现有基础上继续深化合作,发展出更多适用于新型大规模语言模型的优化策略和技术,以满足愈发复杂的计算需求和快速变化的硬件环境。 阅读详情

相关推荐

TVM——快速部署模型的终极方案

作者:禅与计算机程序设计艺术 1.简介 随着人工智能(AI)技术的发展,模型的迅速部署已经成为一种必然。在深度学习模型快速落地到商业落地场景中,如何将高效且准确的推理部署到生产环境并保证服务质量一直是一个重要的课题。本文将通过对TVM工具进行阐述,结合相关开源框架的功能特性和实际案例,对当前的模型部署方

AI天才研究院 2716

[AI算法][模型部署]:TVM模型部署实战

[模型部署]:TVM模型部署实战

Bin_Dut的博客 4057

MLC-LLM:大语言模型高效部署与跨平台推理的编译解决方案

机器学习编译(MLC)是一种将深度学习模型从训练框架转换为高效、可移植部署格式的技术。其核心原理是通过静态计算图优化、算子融合与内存规划,结合目标硬件代码生成与自动调优,显著提升推理性能并降低运行时依赖。这项技术的价值在于解决了模型部署中的硬件绑定与性能瓶颈问题,实现了“一次编译,处处运行”的愿景。在应用场景上,它特别适用于大语言模型(LLM)在云端、边缘设备及移动端的部署。本文聚焦的MLC-LLM框架,正是利用机器学习编译技术,通过量化策略和PagedAttention等优化,为大语言模型提供了一套统一的

weixin_33722405的博客 604

陈天奇TVM团队出品:A卡跑大模型,性能达到4090的80%,价格仅一半

©作者 |泽南来源 |机器之心英伟达 GPU 买不到的问题,就这样解决了?最近,科技领域有很多人都在为算力发愁。▲ OpenAI CEO奥特曼:我整天在都想着flops自预训练大模型兴起以来,人们面临的算力挑战就变得越来越大。为此,人们为大语言模型(LLM)提出了许多训练和推理的解决方案。显然,大多数高性能推理解决方案都基于 CUDA 并针对英伟达 GPU 进行了优化。但在动辄千亿参数的模型体...

Paper weekly 1181

Cadence Allegro 17.2 PCB设计避坑指南:从焊盘制作到封装绘制的完整流程

本文详细解析了Cadence Allegro 17.2在PCB设计中的关键操作技巧,从焊盘制作到封装绘制的完整流程。通过0402电阻和2.54mm排针的实例,揭示新手常见错误及解决方案,包括焊盘参数设置、层类型区分和坐标输入规范,帮助工程师避免设计返工和生产事故。

weixin_28683391的博客 409

地质工程师必备:Dips软件从数据导入到主结构面分析的完整流程解析

本文详细解析了地质工程师使用Dips软件进行主结构面分析的完整流程。从数据准备与导入、结构面分组与扇形区划分,到主结构面计算与工程解读,系统阐述了如何将野外节理数据转化为控制岩体稳定性的关键信息。重点介绍了利用等值线图识别优势组、计算统计主值及其置信椭圆,并强调了地质判断与工程应用结合的重要性。

table的专栏 341

深度学习模型编译框架TVM概述

★在任意深度学习的应用场景落地一个模型/算法时,需要经历两个基本步骤:1. 根据数据生产一个模型的训练步骤;2. 将生产出的模型部署到目标设备上执行服务的推理步骤。训练步骤目前基本由Ten...

LF_AI的博客 2409

openMVS ---dense point cloud(代码理解)

openMVS ---dense point cloud(代码理解)0 代码实现原理1 代码实现步骤1.1 参数传递1.2 loading .mvs数据1.3 稠密重建1.3.0 相关变量及概念1.3.1 ComputeDepthMaps(data) --计算多张深度图1.3.1.1. pre1.3.1.2 为每一张depth map Select view(s) !!!1.3.1.2.1 N neighbors --- global score + filter1.3.1.2.2 MRF ---- t

小rivaaaa的博客 5328

TVM大语言模型优化终极指南:从量化到部署的完整解决方案

Apache TVM作为深度学习编译器领域的领先者,为大语言模型的优化和部署提供了完整的解决方案。在当今AI应用爆炸式增长的时代,如何高效地在各种硬件平台上部署大语言模型成为开发者面临的重要挑战。TVM通过其独特的中间表示和优化技术,让模型量化变得简单高效。 ## 🔥 为什么大语言模型需要优化? 大语言模型通常包含数十亿甚至上万亿参数,直接部署会面临: - **内存占用过高**:模型参数占用

gitblog_00726的博客 954

从Chimera到ChimeraX:科研绘图升级指南,让你的论文配图更专业

本文详细介绍了从UCSF Chimera升级到ChimeraX的科研绘图指南,帮助研究者提升论文配图质量。ChimeraX在渲染引擎、大分子处理能力和动画功能上具有显著优势,特别适合生命科学和结构生物学领域。文章涵盖材质设置、专业配色、工作流程和期刊适配等核心技巧,助力产出出版级分子可视化图像。

weixin_33670786的博客 933

2026年CSP-J初赛真题及答案解析

2026年CSP-J初赛真题及题解

王老师青少年编程 1627

FlashInfer与主流框架集成指南:PyTorch、TVM和TensorRT-LLM

FlashInfer是一个专为大语言模型服务优化的高性能GPU内核库,提供了FlashAttention、PageAttention和LoRA等关键算法的实现。本文将详细介绍FlashInfer如何与PyTorch、TVM和TensorRT-LLM等主流深度学习框架进行无缝集成,帮助开发者在LLM推理场景中实现极致的性能优化。🚀 ## FlashInfer核心功能与架构概述 FlashInf

gitblog_00634的博客 1009

关于 ABAP OPEN SQL 的动态 WHERE 条件和 Dynamic Selections

ABAP动态查询技术解析 本文介绍了ABAP中实现灵活数据库查询的动态选择技术,通过FREE_SELECTIONS函数组实现用户自定义查询条件。核心内容包括: 传统固定WHERE条件的局限性,无法满足业务多变需求 Dynamic Selections机制的工作原理,使用FREE_SELECTIONS_INIT初始化、FREE_SELECTIONS_DIALOG交互 完整Demo程序演示如何构建通用表数据浏览器: 用户输入表名后校验权限 调用动态选择函数生成条件 动态拼接WHERE子句执行查询 使用CL_SA

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 676

TVM Unity:揭秘下一代统一静态图与动态图编译的架构

TVM Unity是Apache TVM社区推出的下一代机器学习编译架构,针对静态图与动态图编译的统一问题提出了创新性解决方案。本文深入剖析了TVM Unity的设计哲学、IRModule核心抽象、动态形状支持、可组合编译流水线等关键技术,并通过实际案例展示其在多硬件平台上的优化效果。TVM Unity通过引入Relax抽象与TensorIR的深度融合,解决了传统编译栈在动态图支持与跨层级优化方面的局限性,为机器学习模型部署提供了更灵活、高效的编译基础设施。

AladdinEdu,你的AI学习实践工作坊。让想法落地,让研究加速。助力高校AI人才成长,点亮创新未来。 1155

Win10 系统下VisualStudio2022 配置Open3D-0.18.0(C++)

Win10 系统下VisualStudio2022 配置Open3D-0.18.0

点云侠的博客 2657

一个非常简单的变分量子分类器 (VQC)

​ 在之前的帖子(这里和这里)中,我已经开始谈论 QML,为什么以及如何学习,从现在开始,我将开始分享我的研究和发现,到目前为止,这些都是非常基本的。​

gongdiwudu的专栏 6208

基于WebGPU与TVM的浏览器端AI聊天应用部署指南

深度学习模型部署正从云端向边缘端演进,其核心在于将计算任务从远程服务器迁移到本地设备。这一过程依赖于模型编译与硬件加速两大关键技术原理。模型编译器(如Apache TVM)能够将训练好的神经网络模型转换为针对特定硬件优化的可执行格式,而现代图形API(如WebGPU)则提供了直接调用本地GPU算力的底层接口。这种技术组合的价值在于实现了真正的隐私保护、零网络延迟和离线可用性,尤其适用于对数据安全要求高的场景。在应用层面,通过将大语言模型(如Vicuna-7B)编译为WebGPU支持的格式,并利用Next.j

weixin_33693070的博客 398

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

2023 Meet TVM 深圳站报名开启!

9 月中旬,TVM Meetup 第 3 站线下聚会定档深圳,这次我们邀请到了 5 位资深 AI 编译器专家,他们将在深圳腾讯大楼,为在场的朋友们带来精彩分享。

Tencnt_news的博客 352

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Web LLM Chat:浏览器本地运行大语言模型的技术解析与实践指南

大语言模型(LLM)作为当前人工智能领域的核心技术,其部署方式正从云端向边缘端扩展。通过机器学习编译(MLC)技术,可以将PyTorch等框架训练的模型编译优化,实现跨平台高效部署。WebGPU与WebAssembly为浏览器提供了接近原生的高性能计算能力,使得在资源受限的环境中运行计算密集型任务成为可能。这种本地化部署方案在保障数据隐私、降低网络依赖的同时,为实时交互应用提供了新范式。本文以Web LLM Chat项目为例,深入探讨了如何在浏览器中利用WebGPU和量化技术,实现Llama 2等大模型的本

weixin_30654419的博客 417

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

基于WebGPU与TVM的浏览器端大模型部署:ChatLLM-Web技术解析与实践

深度学习模型部署正从云端向边缘端演进,其核心在于将训练好的模型高效适配到多样化的硬件环境中。模型编译技术通过图优化、算子融合与内存规划,将通用模型转换为针对特定硬件的高性能代码,从而释放终端设备的算力潜力。WebGPU作为新一代浏览器通用计算标准,为前端提供了直接调用GPU进行大规模并行计算的能力,使得在浏览器中运行计算密集型任务成为可能。结合TVM/MLC编译栈,开发者能够将大语言模型编译优化为WebGPU可执行的格式,实现模型在浏览器端的原生推理。这一技术路径在隐私保护、低延迟响应和离线可用性方面具有显

weixin_30729609的博客 398

冲破“拼图式”编译梦魇:深度解析 Apache TVM (Unity 架构演进版)

2026年生成式AI推理的主战场已转向端侧与边缘计算,传统深度学习编译器在处理大语言模型时面临动态形状失控和算子拼接困难等挑战。Apache TVM Unity架构通过三大革新突破僵局:1)引入Relax抽象层,原生支持动态形状追踪,消除显存浪费;2)采用Python-First设计,实现模块化组合与自定义算子无缝插拔;3)构建轻量级Relax虚拟机,运行时仅数百KB,支持跨平台部署。该架构将硬件优化转化为编译期数学变换,使端侧AI部署从手工调优升级为自动化工程,成为支撑MLC LLM等主流框架的核心引擎,

greenspan的专栏 367

活动回顾 | 暴雨也无法阻挡的奔赴,2023 Meet TVM · 深圳站完美收官!

2023 Meet TVM · 深圳站于 2023 年 9 月 16 日在腾讯大厦成功举办,百余名参与者亲临现场,聆听讲师们的精彩分享。 内含活动录像及讲师 PPT

HyperAI超神经 390

早鸟票倒计时|TVM/Triton/TileLang同台炫技,Meet AI Compiler邀你一同解锁AI编译器的无限可能!

早鸟票倒计时|TVM/Triton/TileLang同台炫技,Meet AI Compiler邀你一同解锁AI编译器的无限可能!

HyperAI超神经 1757

AI Complier明日开讲丨AMD/沐曦/字节/北大线下集结,TVM/Triton/TileLang各展所长

2022 年 11 月,在 MLC.AI 社区志愿者的共同努力下,首个完整的 TVM 中文文档上线,并成功托管至 HyperAI超神经官网,进一步为对机器学习编译感兴趣的国内开发者,提供了接触并学习一门新技术的基础设置——文档。旨在通过提供行业资讯报道、数据集加速下载、在线教程演示、热门模型性能评测、前沿论文推荐、高价值成果解读、顶会日历集成等一系列服务,助力全球数据科学及⼈⼯智能⾏业的开发者及爱好者学习、理解、实践,与社区⼀起构建⼈⼯智能的未来。这次讨论主要围绕如何在沐曦 GPU 上应用 TVM。

HyperAI超神经 1130

MLC LLM:将大模型运行在手机端的部署工具

MLC LLM依赖于开源生态系统,更具体地说,是 TVM Unity,这是 TVM 项目中一个令人兴奋的最新发展,它支持 Python 优先的交互式 MLC 开发体验,使我们能够轻松地用 Python 编写新的优化,并逐步将我们的应用带到感兴趣的环境中。利用优化,如融合量化内核,一流的动态形状支持和不同的 GPU 后端。提供了一个轻量级的基于 C + + 的示例 CLI 应用程序,它展示了如何包装已编译的构件和必要的预/后处理,这将有望澄清工作流程,以便将它们嵌入到本地应用程序中。

小鹏AI 6205

mlc-llm 推理优化和大语言模型搭建解析

本文解析一下mlc-llm(https://github.com/mlc-ai/mlc-llm)对大模型推理的流程以及使用的图优化,算子优化策略。mlc-llm的模型部署流程可以查看官方文档:https://mlc.ai/mlc-llm/docs/ ,也可以参考我前段时间写的这篇。此外,阅读mlc-llm的代码还需要理解一些TVM Unify的一些基础概念,可以参考等等。从 https://github.com/BBuf/tvm_mlir_learn 这里可以查看更多相关博客和资料。在。

I good vegetable a! 2341
上一篇: AI 论文周报丨通用Agent开发/目标检测/开源物理推理模型……一文了解 AI 前沿动态
下一篇: 首个天文多模态基础模型AION-1诞生!UC伯克利等基于2亿天文目标预训练,成功构建泛化性多模态天文AI框架
HyperAI超神经
博客等级 码龄7年 8872粉丝 1753原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值