首页 > 编程语言 >地平线轨迹预测 QCNet 参考算法-V1.0

地平线轨迹预测 QCNet 参考算法-V1.0

时间:2024-09-14 11:17:03浏览次数:11  
标签:dim angle nn self torch QCNet agent V1.0 地平线

该示例为参考算法,仅作为在 征程6 上模型部署的设计参考,非量产算法。

01 简介

轨迹预测任务的目的是在给定历史轨迹的情况下预测未来轨迹。这项任务在自动驾驶、智能监控、运动分析等领域有着广泛应用。传统方法通常直接利用历史轨迹来预测未来,而忽略了预测目标的上下文或查询信息的影响。这种忽视可能导致预测精度的下降,特别是在复杂场景中。

QCNet(Query-Centric Network)引入了一种 query-centric 的预测机制,通过对查询进行显式建模,增强了对未来轨迹的预测能力。首先,通过处理所有场景元素的局部时空参考框架和学习独立于全局坐标的表示,可以缓存和复用先前计算的编码,另外不变的场景特征可以在所有目标 agent 之间共享,从而减少推理延迟。其次,使用无锚点查询来周期性检测场景上下文,并且在每次重复时解码一小段未来的轨迹点。这种基于查询的解码管道将无锚方法的灵活性融入到基于锚点的解决方案中,促进了多模态和长期时间预测的准确性。

本文将介绍轨迹预测算法 QCNet 在地平线 征程6 平台上的优化部署。

02 性能精度指标

模型参数:

性能精度表现:

03 公版模型介绍

由于轨迹预测的归一化要求,现有方法采用以 agent 为中心的编码范式来实现空间旋转平移不变性,其中每个代理都在由其当前时间步长位置和偏航角确定的局部坐标系中编码。但是观测窗口每次移动时,场景元素的几何属性需要根据 agent 最新状态的位置重新归一化,不断变化的时空坐标系统阻碍了先前计算编码的重用,即使观测窗口存在很大程度上的重叠。为了解决这个问题, QCNet 引入了以查询为中心的编码范式,为查询向量派生的每个场景元素建立一个局部时空坐标系,并在其局部参考系中处理查询元素的特征。然后,在进行基于注意力的场景上下文融合时,将相对时空位置注入 Key 和 Value 元素中。下图展示了场景元素的局部坐标系示例:

QCNet 主要由编码器和解码器组成,其作用分别为:

  • 编码器:对输入的场景元素进行编码,采用了目前流行的 factorized attention 实现了时间维度 attention、Agent-Map cross attention 和 Agent与Agent 间隔的 attention;
  • 解码器:借鉴 DETR 的解码器,将编码器的输出解码为每个目标 agent 的 K 个未来轨迹。

3.1 以查询为中心的场景上下文编码

QCNet 首先进行了场景元素编码、相对位置编码和地图编码,对于每个 agent 状态和 map 上的每个采样点,将傅里叶特征与语义属性(例如:agent 的类别)连接起来,并通过 MLP 进行编码,为了进一步生成车道和人行横道的多边形级表示,采用基于注意力的池化对每个地图多边形内采样点进行。这些操作产生形状为[A, T, D]的 agent 编码和形状为[M, D]的 map 编码,其中 D 表示隐藏的特征维度。为了帮助 agent 编码捕获更多信息,编码器还考虑了跨 agent 时间 step、agent 之间以及 agent 与 map 之间的注意力并重复多次。如下图所示:

3.2 基于查询的轨迹解码

轨迹预测的第二步是利用编码器输出的场景编码来解码每个目标 agent 的 K 个未来轨迹。受目标检测任务的启发,采用类似 detr 的解码器来处理这种一对多问题,并且利用了一个递归的、无锚点的 proposal 模块来生成自适应轨迹锚点,然后是一个基于锚点的模块,进一步完善初始 proposals。相关流程如下所示:

04 地平线部署优化

整体情况:

QCNet 网络主要由 MapEncoder, AgentEncoder, QCDecoder 构成,其中 MapEncoder 计算地图元素 embedding,AgentEncoder 计算 agent 元素 embedding,核心组件为 FourierEmbedding 和 AttentionLayer。

改动点:

  1. 优化 FourierEmbedding 结构,去除其中的所有 edge_index,直接计算形状为[B, lenq, lenk, D]的相对信息 r;
  2. 将 AttentionLayer 中的 query 形状设为[B, lenq, 1, D] , key 形状为[B, 1, lenk, D], r 形状为[B, lenq, lenk, D],利于性能提升;

4.1 性能优化

4.1.1 代码重构

FourierEmbedding 将每个场景元素的极坐标转换成傅里叶特征,以方便高频信号的学习。但是公版 QCNet 使用了大量 edge_index 索引操作, 使得模型中存在大量 BPU 暂不支持的 index_select、scatter 等操作。QCNet 参考算法重构了代码,去除了 FourierEmbedding 中的所有 edge_index,agent_encoder 编码器注意力层的 query 形状设为[B, lenq, 1, D] , key 形状为[B, 1, lenk, D], r 形状为[B, lenq, lenk, D],相关代码如下所示:

    def _attn_block(
        self,
        x_src,
        x_dst,
        r,
        mask=None,
        extra_1dim=False,
    ):
        B = x_src.shape[0]
        if extra_1dim:
            ...
        else:
            if x_src.dim() == 4 and x_dst.dim() == 3:
                lenq, lenk = x_dst.shape[1], x_src.shape[2]
                kdim1 = lenq
                qdim = 1
            elif x_src.dim() == 3:
                kdim1 = qdim = 1
                lenq = x_dst.shape[1]
                lenk = x_src.shape[1]
            #重构q,k,v,rk,rv的shape
            q = self.to_q(x_dst).view(
                B, lenq, qdim, self.num_heads, self.head_dim
            )  # [B,pl, 1, h, d]
            k = self.to_k(x_src).view(
                B, kdim1, lenk, self.num_heads, self.head_dim
            )  # [B,pl, pt, h, d]
            v = self.to_v(x_src).view(
                B, kdim1, lenk, self.num_heads, self.head_dim
            )  # [B,pl, pt, h, d]
            if self.has_pos_emb:
                rk = self.to_k_r(r).view(
                    B, lenq, lenk, self.num_heads, self.head_dim
                )
                rv = self.to_v_r(r).view(
                    B, lenq, lenk, self.num_heads, self.head_dim
                )
        if self.has_pos_emb:
            k = k + rk
            v = v + rv
        #计算相似性
        sim = q * k
        sim = sim.sum(dim=-1)
        #self.scale = head_dim ** -0.5
        sim = sim * self.scale  # [B, pl, pt, h]
        if mask is not None:
            sim = torch.where(
                mask.unsqueeze(-1),
                sim,
                self.quant(torch.tensor(-100.0).to(mask.device)),)
        attn = torch.softmax(sim, dim=-2)  # [B, pl, pt, h]
        ...
        if extra_1dim:
            inputs = out.view(B, ex_dim, -1, self.num_heads * self.head_dim)
        else:
            inputs = out.view(B, -1, self.num_heads * self.head_dim)
        x = torch.cat([inputs, x_dst], dim=-1)
        g = torch.sigmoid(self.to_g(x))
        #重构代码后,edge_index也就不需要了,省去了仅能用CPU运行的索引类算子
        #agg = self.propagate(edge_index=edge_index, x_dst=x_dst, q=q, k=k, v=v, r=r)
        agg = inputs + g * (self.to_s(x_dst) - inputs)
        return self.to_out(agg)

代码路径:`/usr/local/lib/python3.10/dist-packages/hat/models/task_modules/qcnet/rattention.py

4.1.2 FourierConvEmbedding

为了提升性能,主要对 FourierConvEmbedding 做了以下改进:

  1. Embedding 和 Linear 层全部替换为了对 BPU 更友好的 Conv1x1;
  2. 删除 self.mlps 层中的 LayerNorm,对精度基本无影响;
  3. 将公版代码中的torch.stack(continuous_embs).sum(dim=0)直接优化为了 add 操作,从而获得了比较大的性能收益。

对应代码如下所示:

class FourierConvEmbedding(nn.Module):
    def __init__(
        self, input_dim: int, hidden_dim: int, num_freq_bands: int
    ) -> None:
        super(FourierConvEmbedding, self).__init__()
        self.input_dim = input_dim
        self.hidden_dim = hidden_dim
        #nn.Embedding替换为了Conv1x1
        self.freqs = nn.ModuleList( [
                nn.Conv2d(1, num_freq_bands, kernel_size=1, bias=False)
                for _ in range(input_dim)])
        #Linear层替换为了Conv1x1
        self.mlps = nn.ModuleList(
            [nn.Sequential(
                    nn.Conv2d(
                        num_freq_bands * 2 + 1, hidden_dim, kernel_size=1),
                    #删除LayerNorm
                    #nn.LayerNorm(hidden_dim),
                    nn.ReLU(inplace=True),
                    nn.Conv2d(hidden_dim, hidden_dim, kernel_size=1),)
                for _ in range(input_dim)
            ]
        )
        #Linear层替换为了Conv1x1
        self.to_out = nn.Sequential(
            LayerNorm((hidden_dim, 1, 1), dim=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(hidden_dim, hidden_dim, 1),
        )
        ...
    def forward(
        self,
        continuous_inputs: Optional[torch.Tensor] = None,
        categorical_embs: Optional[List[torch.Tensor]] = None,
    ) -> torch.Tensor:
        if continuous_inputs is None:
            ...
        else:
            continuous_embs = 0
            for i in range(self.input_dim):
                ...
                if i == 0:
                    continuous_embs = self.mlps[i](x)
                else:
                    #将stack+sum的操作替换为add
                    continuous_embs = continuous_embs + self.mlps[i](x)
            # x = torch.stack(continuous_embs, dim=0).sum(dim=0)
            x = continuous_embs
            if categorical_embs is not None:
                #将stack+sum的操作替换为add
                # x = x + torch.stack(categorical_embs, dim=0).sum(dim=0)
                x = x + categorical_embs
        return self.to_out(x)

代码路径:`/usr/local/lib/python3.10/dist-packages/hat/models/task_modules/qcnet/fourier_embedding.py

4.1.3 RAttentionLayer

为了提升性能,去除 RAttentionLayer 的对相对时空编码 r 的 LayerNorm,相关代码如下:

class RAttentionLayer(nn.Module):
    def __init__(
        self,
        ...
        
    def forward(self, x, r, mask=None, extra_dim=False):
        if isinstance(x, torch.Tensor):
            ...
        else:
            x_src, x_dst = x
            ...
            x = x[1]
        #取消了公版中对相对时空编码r的LayerNorm
        #if self.has_pos_emb and r is not None:
            #r = self.attn_prenorm_r(r)
        attn = self._attn_block(
            x_src, x_dst, r, mask=mask, extra_1dim=extra_dim
        )  # [B, pl, h*d]
        x = x + self.attn_postnorm(attn)
        x2 = self.ff_prenorm(x)
        x = x + self.ff_postnorm(self.ff_mlp(x2))

        return x

代码路径:`/usr/local/lib/python3.10/dist-packages/hat/models/task_modules/qcnet/rattention.py

4.2 量化精度优化

4.2.1 FourierConvEmbedding

QCNetMapEncoder QCNetAgentEncode 的输入中存在距离计算、torch.norm 等对量化不友好的操作,为了提升量化精度,将输入全部置于预处理中,相关代码如下所示:

class QCNetOEAgentEncoderStream(nn.Module):
    def __init__(
        self,
        ...
    ) -> None:
        super().__init__()
    def build_cur_r_inputs(self, data, cur):
        pos_pl = data["map_polygon"]["position"] / 10.0
        orient_pl = data["map_polygon"]["orientation"]
        pos_a = data["agent"]["position"][:, :, :cur] / 10.0  # [B, A, HT, 2]
        head_a = data["agent"]["heading"][:, :, :cur]  # [B, A, HT]
        vel = data["agent"]["velocity"][:, :, :cur, : self.input_dim] / 10.0
        ...
    def build_cur_embs(self, data, cur, map_data, x_a_his, categorical_embs):
        B, A = data["agent"]["valid_mask"].shape[:2]
        D = self.hidden_dim
        ST = self.time_span
        pl_N = map_data["x_pl"].shape[1]
        mask_a_cur = data["agent"]["valid_mask"][:, :, cur - 1]
        ....

代码路径:/usr/local/lib/python3.10/dist-packages/hat/models/task_modules/qcnet/agent_st_modeule.py

4.2.2 量化配置

首先使用 QAT 的精度 debug 工具获取量化敏感节点,然后在 Calibration 和量化训练时,对 20% 敏感节点配置为 int16 量化,相关代码如下:

if os.path.exists(sensitive_path2):
    sensitive_table1 = torch.load(sensitive_path1)
    sensitive_table2 = torch.load(sensitive_path2)
    cali_qconfig_setter = (
        sensitive_op_calibration_8bit_weight_16bit_act_qconfig_setter(
            sensitive_table1,
            ratio=0.2,
        ),
        sensitive_op_calibration_8bit_weight_16bit_act_qconfig_setter(
            sensitive_table2,
            ratio=0.2,
        ),
        default_calibration_qconfig_setter,
    )
    qat_qconfig_setter = (
        sensitive_op_qat_8bit_weight_16bit_fixed_act_qconfig_setter(
            sensitive_table1,
            ratio=0.2,
        ),
        sensitive_op_qat_8bit_weight_16bit_fixed_act_qconfig_setter(
            sensitive_table2,
            ratio=0.2,
        ),
        default_qat_fixed_act_qconfig_setter,
    )
    print("Load sensitive table!")

4.3 不支持算子替换

4.3.1 cumsum

公版模型的QCNetDecoder中使用了 征程6 暂不支持的 torch.cumsum 算子,参考算法中将其替换为了 Conv1x1,相关代码如下:

        self.loc_cumsum_conv = nn.Conv2d(
            self.num_future_steps,
            self.num_future_steps,
            kernel_size=1,
            bias=False,
        )
        self.scale_cumsum_conv = nn.Conv2d(
            self.num_future_steps,
            self.num_future_steps,
            kernel_size=1,
            bias=False,
        )

代码路径:/usr/local/python3.10/dist-packages/hat/models/models/task_moddules/qcnet/qc_decoder.py

*** ***

*4.3.2 取余操作*

公版的 AgentEncoder 使用了处于操作“%”用于 wrap_angle,此操作当前仅支持在 CPU 上运行,为了提升性能,将其替换为了 torch.where 操作,代码对比如下所示:

公版:

def wrap_angle(
  angle: torch.Tensor,
  min_val: float = -math.pi,
  max_val: float = math.pi) -> torch.Tensor:
  return min_val + (angle + max_val) % (max_val - min_val)

参考算法:

def wrap_angle(
  angle: torch.Tensor, min_val: float = -math.pi, max_val: float = math.pi
  ) -> torch.Tensor:
  angle = torch.where(angle < min_val, angle + 2 * math.pi, angle)
  angle = torch.where(angle > max_val, angle - 2 * math.pi, angle)
  return angle

代码路径:/usr/local/python3.10/dist-packages/hat/models/models/task_moddules/qcnet/utils.py

05 总结与建议

**5.1 *index_select 和 scatter 算子*

**

征程6 仅支持 index_select 和 scatter 索引类算子的 CPU 运行,计算效率较低。QCNet 通过重构代码的形式,优化掉了 index_select 和 scatter 操作,实现了性能的提升。

5.2 ScatterND算子

模型中 nn.embedding 操作引入了目前仅支持在 CPU上 运行的 GatherND 算子,后续将考虑进行优化。

06 附录

  1. 论文:QCNet
  2. 公版模型代码:https://github.com/ZikangZhou/QCNet

标签:dim,angle,nn,self,torch,QCNet,agent,V1.0,地平线
From: https://www.cnblogs.com/horizondeveloper/p/18413562

相关文章

  • 《DNK210使用指南 -CanMV版 V1.0》第二十四章 LCD显示实验
    第二十四章LCD显示实验1)实验平台:正点原子DNK210开发板2)章节摘自【正点原子】DNK210使用指南-CanMV版V1.03)购买链接:https://detail.tmall.com/item.htm?&id=7828013987504)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/k210/ATK-DNK210.html5)正点原......
  • 分类记账小程序系统V1.0.1
    一款支持多人协作的记账本小程序,可用于家庭,团队,组织以及个人的日常收支情况记录,支持周月年度统计V1.0.1修复已知BUG1、新增备案号填写入口2、小程序我的页面底部加入备案号展示3、修复已知小BUG......
  • 西陆旅游系统V1.0.0
    旅游系统,包含消费者端(手机端)、机构工作人员(手机端)、机构端(PC)、平台管理端(PC)。机构可以发布旅游线路、景点项目;用户在线购买订票,支持成人价和儿童价两种票价,支持工作人员现场扫码核销,支持二级分销(高级授权)。V1.0.0发布版本......
  • 嘀嗒陪护小程序V1.0.7
    原生微信小程序开发的陪诊陪护小程序,支持多运营区,陪护师、推广者等完整闭环功能,快速搭建陪护业务平台。提供全部无加密源码,支持私有化部署。V1.0.7自定义文字,可指定陪护师1.各运营区可选择是否开启允许用户指定陪护师进行服务2.自定义部分文字(身份资料,提现资料)......
  • 会议室预约小程序V1.0.0
    会议室预约,支持设置免费预约和付费预约(高级授权)、积分兑换商城(高级授权)、积分签到(高级授权)等。提供全部前后端无加密源代码、数据私有化部署。V1.0.0发布版本......
  • 西陆二手交易系统V1.0.1
    二手交易系统,卖家可以发布二手信息,买家可以在线询价,支持在线聊天(高级授权),在线购买支付,支持发布高价回收(高级授权)信息。自带社交板块,用户可以发布帖子、加入圈子、关注好友。V1.0.1bug修复与优化优化与bug1.留言增加小程序文本检测2.修复评论时间转换方法bug3.后台分类删除bug4.......
  • 工具箱、多种灵活的代码采纳方式等6项功能升级,CodeGeeX v1.0.4版本上线Visual Studio
    CodeGeeXv1.0.4版本上线VisualStudio插件市场,这个版本为VisualStudio平台上的开发者带来了多项新功能和性能优化,以便于更好的利用智能辅助编程助手CodeGeeX插件,提升编程体验。新功能亮点速览:1.侧边栏工具箱功能v1.0.4版本中,CodeGeeX新增了侧边栏工具箱功能。在工具箱中,可以根据......
  • 露营地管理小程序V1.0.0
    现代化的露营地管理小程序,是专为露营业务设计开发小程序应用。平台拥有多角色管理,同时具有营位预定、门票购买等功能模块。V1.0.0发布版本......
  • 课程预约小程序V1.0.1
    专属课程预约小程序,程序适用于SPA瑜伽、普拉提舍宾、培训机构等场所,通过多角色身份进行管理,让你的瑜伽馆/培训机构更加操作便捷。Uniapp小程序端包含会员入口、老师入口、员工入口。V1.0.1安全更新修复提现接口安全风险1.修复提现接口安全风险,请务必更新升级。......
  • XYvenue场地预定小程序V1.0.3
    多场馆场地预定小程序,提供运动场馆运营解决方案,适用于体育馆、羽毛球馆、兵乒球馆、篮球馆、网球馆等场馆。V1.0.3修复退订时间设置不生效问题......