Dieser Artikel wird mit Genehmigung des öffentlichen Kontos von Autonomous Driving Heart nachgedruckt. Bitte wenden Sie sich für einen Nachdruck an die Quelle.
Ich glaube, dass mit Ausnahme einiger großer Hersteller selbst entwickelter Chips die meisten Unternehmen für autonomes Fahren NVIDIA-Chips verwenden werden, die nicht von TensorRT getrennt werden können. TensorRT ist ein C++-Inferenz-Framework, das auf verschiedenen NVIDIA-GPU-Hardwareplattformen läuft. Das Modell, das wir mit Pytorch, TF oder anderen Frameworks trainiert haben, kann zuerst in das ONNX-Format und dann in das TensorRT-Format konvertiert werden. Anschließend können wir unser Modell mithilfe der TensorRT-Inferenz-Engine ausführen, wodurch die Geschwindigkeit beim Ausführen dieses Modells auf NVIDIA-GPUs verbessert wird .
Im Allgemeinen unterstützen onnx und TensorRT nur relativ feste Modelle (einschließlich fester Eingabe- und Ausgabeformate auf allen Ebenen, einzelner Zweige usw.) und unterstützen höchstens die äußerste dynamische Eingabe (der Export von onnx kann durch Festlegen des Parameters „dynamic_axes“ bestimmt werden). um dynamische Dimensionsänderungen zu ermöglichen. Aber Freunde, die an der Spitze der Wahrnehmungsalgorithmen stehen, werden wissen, dass ein wichtiger Entwicklungstrend End-2-End ist, der Zielerkennung, Zielverfolgung, Flugbahnvorhersage, Entscheidungsplanung usw. abdecken kann Als typisches Beispiel kann das MUTR3D-Modell verwendet werden, das eine durchgängige Zielerkennung und Zielverfolgung ermöglicht (eine Einführung in das Modell finden Sie unter). :)
In MOTR/MUTR3D werden wir die Theorie und Beispiele des Etikettenzuweisungsmechanismus detailliert erläutern, um eine echte End-to-End-Verfolgung mehrerer Objekte zu erreichen. Bitte klicken Sie auf den Link, um mehr zu lesen: https://zhuanlan.zhihu.com/p/609123786
Bei der Konvertierung dieses Modells in das TensorRT-Format und der Erzielung einer präzisen Ausrichtung, sogar einer fp16-Präzisionsausrichtung, kann es zu einer Reihe dynamischer Elemente kommen, zum Beispiel: mehrere if-else-Zweige, dynamische Änderungen in Subnetzwerk-Eingabeformen und andere Operationen und Operatoren, die eine dynamische Verarbeitung erfordern usw.
Bilder
MUTR3D-Architektur Da der gesamte Prozess viele Details umfasst, ist die Situation unterschiedlich Es ist schwierig, eine Plug-and-Play-Lösung zu finden, indem man sich die Referenzmaterialien im gesamten Netzwerk ansieht oder sogar auf Google sucht. Sie kann nur durch kontinuierliches Aufteilen und Experimentieren gelöst werden. Durch mehr als einen Monat intensiver Erkundung Übung des Bloggers (Vorherige Erfahrung mit TensorRT Nicht viel, ich habe sein Temperament nicht verstanden), ich habe viel Köpfchen eingesetzt und bin auf viele Fallstricke gestoßen. Schließlich habe ich die Konvertierung erfolgreich durchgeführt und eine fp32/fp16-Präzisionsausrichtung erreicht. und der Verzögerungsanstieg war im Vergleich zur einfachen Zielerkennung sehr gering. Ich möchte hier eine einfache Zusammenfassung erstellen und eine Referenz für alle bereitstellen (ja, ich habe Rezensionen geschrieben und schließlich über die Praxis geschrieben!)
Zuallererst ist das Datenformat von MUTR3D ziemlich Speziell, und alle Beispiele werden verwendet. Dies liegt daran, dass jede Abfrage an viele Informationen gebunden ist und für einen einfacheren Eins-zu-Eins-Zugriff in Instanzen gepackt wird. Für die Bereitstellung können die Eingabe und Ausgabe jedoch nur Tensoren sein, also die Instanzdaten Muss zuerst zerlegt werden, wird zu mehreren Tensorvariablen. Und da die Abfrage und andere Variablen des aktuellen Frames im Modell generiert werden, müssen Sie nur die im vorherigen Frame beibehaltene Abfrage und andere Variablen eingeben und die beiden im model.
Bei der Eingabevorbestellungsrahmenabfrage und anderen Variablen besteht ein wichtiges Problem darin, dass die Form unsicher ist. Dies liegt daran, dass MUTR3D nur Abfragen behält, die in vorherigen Frames Ziele erkannt haben. Dieses Problem ist relativ einfach zu lösen. Der einfachste Weg ist das Auffüllen, also das Auffüllen auf eine feste Größe. Für die Abfrage können Sie alle Nullen zum Auffüllen verwenden. Die entsprechende Zahl kann durch Experimente basierend auf Ihren eigenen Daten ermittelt werden. Zu wenige werden das Ziel leicht verfehlen, zu viele werden Platz verschwenden. Obwohl der Parameter „dynamic_axes“ von onnx eine dynamische Eingabe realisieren kann, sollte es ein Problem geben, da es sich um die vom nachfolgenden Transformator berechnete Größe handelt. Ich habe es nicht ausprobiert, Leser können es versuchen
Wenn Sie keine speziellen Operatoren verwenden, können Sie nach dem Auffüllen erfolgreich in ONNX und TensorRT konvertieren. Diese Situation wird in der Praxis sicherlich vorkommen, sie würde jedoch den Rahmen dieses Artikels sprengen. Beispielsweise wird in MUTR3D die Verwendung des Torch.linalg.inv-Operators zum Suchen der Pseudoinversmatrix beim Verschieben des Referenzpunkts zwischen Frames nicht unterstützt. Wenn Sie auf einen nicht unterstützten Operator stoßen, können Sie nur versuchen, ihn zu ersetzen. Wenn er nicht funktioniert, können Sie ihn nur außerhalb des Modells verwenden. Da dieser Schritt jedoch in der Vor- und Nachbearbeitung des Modells platziert werden kann, habe ich mich entschieden, ihn außerhalb des Modells zu verschieben. Es wäre schwieriger, eigene Operatoren zu schreiben. Eine erfolgreiche Konvertierung bedeutet nicht, dass alles gut geht. Die Antwort lautet oft nein. Wir werden feststellen, dass die Genauigkeitslücke sehr groß ist. Dies liegt daran, dass das Modell über viele Module verfügt. Lassen Sie uns zunächst über den ersten Grund sprechen. In der Selbstaufmerksamkeitsphase des Transformers findet eine Informationsinteraktion zwischen mehreren Abfragen statt. Das ursprüngliche Modell behält jedoch nur die Abfragen bei, bei denen das Ziel einmal erkannt wurde (im Modell als aktive Abfragen bezeichnet), und nur diese Abfragen sollten mit der Abfrage des aktuellen Frames interagieren. Und da nun viele ungültige Abfragen ausgefüllt werden, wirkt sich die Interaktion aller Abfragen unweigerlich auf die Ergebnisse aus
Die Lösung für dieses Problem wurde von DN-DETR[1] inspiriert, das darin besteht, Attention_mask zu verwenden, das dem Parameter „attn_mask“ in nn.MultiheadAttention entspricht. Seine Funktion besteht zunächst darin, Abfragen zu blockieren, die keine Informationsinteraktion erfordern. Dies liegt daran, dass in NLP alle Sätze eine inkonsistente Länge haben, was genau meinen aktuellen Anforderungen entspricht. Ich muss nur beachten, dass „True“ die Abfrage darstellt, die blockiert werden muss, und „False“ die gültige Abfrage darstellt
Aufmerksamkeitsmaskendiagramm Da die Logik zur Berechnung der Aufmerksamkeitsmaske etwas kompliziert ist, gibt es viele Beim Betrieb und der Konvertierung von TensorRT können neue Probleme auftreten. Daher sollte es außerhalb des Modells berechnet und als Eingabevariable in das Modell eingegeben und dann übergeben werden Zum Transformator. Das Folgende ist der Beispielcode:
data['attn_masks'] = attn_masks_init.clone().to(device)data['attn_masks'][active_prev_num:max_num, :] = Truedata['attn_masks'][:, active_prev_num:max_num] = True[1]DN-DETR: Accelerate DETR Training by Introducing Query DeNoising
obj_mask = (obj_idxs >= 0).float()attn_mask = torch.matmul(obj_mask.unsqueeze(-1), obj_mask.unsqueeze(0)).bool()attn_mask = ~attn_mask
mask = (~attention_mask[-1]).float()track_scores = track_scores * mask
需要重新写的内容是:赋值的值必须是一个,不能是多个。例如,当我更新新出现的目标时,我不会统一赋值为某个ID,而是需要为每个目标赋予连续递增的ID。我想到的解决办法是先统一赋值为一个比较大且不可能出现的数字,比如1000,以避免与之前的ID重复,然后在后续处理中将1000替换为唯一且连续递增的数字。(我真是个天才)
如果要进行递增操作(+=1),只能使用简单的掩码,即不能涉及复杂的逻辑计算。例如,对disappear_time的更新,本来需要同时判断obj_idx >= 0且track_scores = 0这个条件。虽然看似不合理,但经过分析发现,即使将obj_idx=-1的非目标的disappear_time递增,因为后续这些目标并不会被选入,所以对整体逻辑影响不大
综上,最后的动态更新track_id示例代码如下,在后处理环节要记得替换obj_idx为1000的数值.:
def update_trackid(self, track_scores, disappear_time, obj_idxs):disappear_time[track_scores >= 0.4] = 0obj_idxs[(obj_idxs == -1) & (track_scores >= 0.4)] = 1000disappear_time[track_scores 5] = -1
至此模型部分的处理就全部结束了,是不是比较崩溃,但是没办法,部署端到端模型肯定比一般模型要复杂很多.模型最后会输出固定shape的结果,还需要在后处理阶段根据obj_idx是否>0判断需要保留到下一帧的query,再根据track_scores是否>filter score thresh判断当前最终的输出结果.总体来看,需要在模型外进行的操作只有三步:帧间移动reference_points,对输入query进行padding,对输出结果进行过滤和转换格式,基本上实现了端到端的目标检测+目标跟踪.
需要重新写的内容是:以上六点的操作顺序需要说明一下。我在这里按照问题分类来写,实际上可能的顺序是1->2->3->5->6->4,因为第五点和第六点是使用QIM的前提,它们之间也存在依赖关系。另外一个问题是我没有使用memory bank,即时序融合的模块,因为经过实验发现这个模块的提升效果并不明显,而且对于端到端跟踪机制来说,已经天然地使用了时序融合(因为直接将前序帧的查询信息带到下一帧),所以时序融合并不是非常必要
好了,现在我们可以对比TensorRT的推理结果和PyTorch的推理结果,会发现在FP32精度下可以实现精度对齐,非常棒!但是,如果需要转换为FP16(可以大幅降低部署时延),第一次推理会发现结果完全变成None(再次崩溃)。导致FP16结果为None一般都是因为出现数据溢出,即数值大小超限(FP16最大支持范围是-65504~+65504)。如果你的代码使用了一些特殊的操作,或者你的数据天然数值较大,例如内外参、姿态等数据很可能超限,一般可以通过缩放等方式解决。这里再说一下和我以上6点相关的一个原因:
7.使用attention_mask导致的fp16结果为none的问题
这个问题非常隐蔽,因为问题隐藏在torch.nn.MultiheadAttention源码中,具体在torch.nn.functional.py文件中,有以下几句:
if attn_mask is not None and attn_mask.dtype == torch.bool:new_attn_mask = torch.zeros_like(attn_mask, dtype=q.dtype)new_attn_mask.masked_fill_(attn_mask, float("-inf"))attn_mask = new_attn_mask
可以看到,这一步操作是对attn_mask中值为True的元素用float("-inf")填充,这也是attention mask的原理所在,也就是值为1的位置会被替换成负无穷,这样在后续的softmax操作中,这个位置的输入会被加上负无穷,输出的结果就可以忽略不记,不会对其他位置的输出产生影响.大家也能看出来了,这个float("-inf")是fp32精度,肯定超过fp16支持的范围了,所以导致结果为none.我在这里把它替换为fp16支持的下限,即-65504,转fp16就正常了,虽然说一般不要修改源码,但这个确实没办法.不要问我怎么知道这么隐蔽的问题的,因为不是我一个人想到的.但如果使用attention_mask之前仔细研究了原理,想到也不难.
好的,以下是我在端到端模型部署方面的全部经验分享,我保证这不是标题党。由于我对tensorRT的接触时间不长,所以可能有些描述不准确的地方
需要进行改写的内容是:原文链接:https://mp.weixin.qq.com/s/EcmNH2to2vXBsdnNvpo0xw
Das obige ist der detaillierte Inhalt vonPraktischer Einsatz: Dynamisches sequentielles Netzwerk für End-to-End-Erkennung und -Verfolgung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!