Вузли ядер LiDAR і BEV
SimaVoxelize, SimaPillarScatter, SimaBEVPoolPlan і SimaBEVPool виконують етапи PointPillars і
BEVFusion, що не працюють на MLA. Кожен вузол виконує одну операцію бібліотеки ядер SiMa
на вибраній вами цілі. Вузол ніколи не переходить на іншу ціль і не копіює тензор, щоб
змінити його розміщення. Якщо бібліотека ядер відхиляє запит, побудова графа завершується
помилкою з поясненням від бібліотеки ядер.
| Вузол | Операція | Цілі |
|---|---|---|
SimaVoxelize | Точки LiDAR у декорований квантований тензор стовпців | A65, EV74 |
SimaPillarScatter | Ознаки стовпців у щільне полотно BEV | A65, EV74 |
SimaBEVPoolPlan | Калібрування камер у план пулінгу BEV | A65 |
SimaBEVPool | Ознаки глибини й контексту камер у сітку BEV | A65, EV74 |
Це фрагмент побудови графа для використання всередині застосунку. Реалізуйте
read_sweep() у застосунку: функція має повертати непорожній вектор із 4*N значень
float у порядку x, y, z, часовий зсув. Значення N має вміщуватися в INT32. Ця
функція не належить до API Neat. Фрагмент лише будує граф: він не подає кадр і
не отримує вихідний результат.
#include <neat.h>
using namespace simaai::neat;
SimaVoxelizeOptions voxelize;
voxelize.voxel_size = {0.25f, 0.25f, 8.0f};
voxelize.point_cloud_range = {-50.0f, -50.0f, -5.0f, 50.0f, 50.0f, 3.0f};
voxelize.quantization.multiplier = 2.720007f; // 1 / the consuming MLA input's scale
voxelize.quantization.zero_point = 8;
voxelize.max_pillars = 40000;
voxelize.max_points_per_pillar = 64;
voxelize.target = SimaKernelTarget::EV74;
// One sample holds every input tensor of the node, in order.
InputOptions input;
input.payload_type = PayloadType::Tensor;
input.memory_policy = InputMemoryPolicy::Ev74;
input.caps_override = "application/vnd.simaai.tensor, representation=(string)tensor-set, "
"storage=(string)tensorbuffer";
std::vector<float> xyzt = read_sweep(); // x, y, z, time offset per point
const auto n = static_cast<std::int64_t>(xyzt.size() / 4);
auto points = Tensor::from_vector(xyzt, {n, 4}, TensorMemory::EV74);
auto point_count = Tensor::from_vector(std::vector<std::int32_t>{static_cast<std::int32_t>(n)},
{1}, TensorMemory::EV74);
Graph graph;
graph.add(nodes::Input(input));
graph.add(nodes::SimaVoxelize(voxelize));
graph.add(nodes::Output());
auto run = graph.build(TensorList{points, point_count});
Python надає ті самі типи параметрів і фабрики pyneat.nodes.sima_voxelize,
pyneat.nodes.sima_pillar_scatter, pyneat.nodes.sima_bev_pool_plan і pyneat.nodes.sima_bev_pool.
Контракти входів і виходів
Кожен вузол читає тензори свого вхідного зразка в наведеному нижче порядку й публікує свої виходи в наведеному нижче порядку. Кількість тензорів, типи даних і форми перевіряються під час побудови графа.
| Вузол | Входи | Виходи |
|---|---|---|
SimaVoxelize | FP32 points [N,4] (x, y, z, часовий зсув); INT32 point_count [1] | INT8 або BF16 decorated [1,M,T,10]; INT32 coordinates [M,2] як (row, column); INT32 count [1] |
SimaPillarScatter | INT8 або BF16 features [M,C]; INT32 coordinates [M,2]; INT32 count [1] | canvas [1,H,W,C] того самого типу, що й features |
SimaBEVPoolPlan | FP32 camera2lidar, camera_intrinsics і img_aug_matrix [N,4,4]; FP32 lidar_aug_matrix [4,4] | UINT8 plan [P] |
SimaBEVPool | INT8 або BF16 depth; context того самого типу; UINT8 plan [P] | INT8 або BF16 bev [1,nZ*C,nX,nY] |
- SimaVoxelize читає лише перші
point_countточок.max_pillars(M) іmax_points_per_pillar(T) — фіксовані місткості; точки й стовпці понад них відкидаються. Невикористані рядки містять нульову точку квантування та нульові координати. - SimaPillarScatter читає лише перші
countстовпців. Він також приймає ознаки з додатковими осями розміру 1, наприклад[1,M,1,C]від моделі ознак стовпців.Cмає бути кратним 16. Порожні комірки містятьzero_point; це має бути код INT8, який вхідне квантування моделі, що читає полотно, призначає значенню 0.0. Байти полотна розміщені в щільному порядку NHWC (для полотна INT8). - SimaBEVPoolPlan бере свою зрізану піраміду огляду (
xs,ys,ds) і сітку (bx,dx,nx) з перетворення вигляду контрольної точки. ТиповийSimaBEVPoolPlanFormat::V1групує точки за комірками BEV; дляSimaBEVPoolна EV74 використовуйтеSimaBEVPoolPlanFormat::V2разом ізcontext_channels. ТиповоP— розмір плану в найгіршому випадку; байти після використаної довжини плану не записуються. - SimaBEVPool підсумовує
depth x contextза точками плану кожної комірки й квантує суму за допомогоюquantization. Задайтеcellsрівнимnxплану,plan_format—formatплану, а для входів INT8quantization.multiplier—depth_scale * context_scale / output_scale. З типовимSimaBEVPoolStorage::ChannelsLastвходи надходять як[N,fH,fW,D]і[N,fH,fW,C], як їх створюють моделі камер. Вихід публікується як логічний[1,nZ*C,nX,nY]із кроками «канали останніми», тож його байти мають розміщення NHWC[1,nX,nY,nZ*C].SimaBEVPoolStorage::Contiguousвикористовує входи[N,D,fH,fW]і[N,C,fH,fW]та щільний вихід.
Логічні осі та фізичне розміщення
SimaBEVPool розрізняє порядок логічних осей і фізичний порядок байтів. Тензор із
позначкою TensorLayout::HWC має логічні осі [N,H,W,K], а з позначкою
TensorLayout::CHW — [N,K,H,W]. Вузол надає перевагу цій позначці й перевіряє
відповідність кроків значенню input_storage. Для ChannelsLast входи HWC мають
бути щільними, а входи CHW — мати кроки з каналами останніми. Для Contiguous
потрібні щільні входи CHW; позначка HWC відхиляється.
Той самий канонічний порядок осей можна задати через Tensor.axis_semantics;
якщо вказано обидві анотації, вони мають узгоджуватися.
Для входів ChannelsLast без позначки зберігається правило сумісності: щільний
NHWC або нещільний логічний NCHW із кроками «канали останніми». Якщо виміри
розміру 1 роблять логічне подання NCHW також щільним, явно задайте
Tensor.layout = TensorLayout::CHW. У цьому випадку самі кроки не визначають
логічні осі. Виходи Pool мають логічні осі CHW і кроки відповідно до
output_storage; вузол не транспонує й не копіює дані для зміни розміщення.
Метадані квантування
Виходи INT8 містять своє квантування в Tensor.semantic.quant, коли вузол його знає:
scale = 1 / quantization.multiplier для SimaVoxelize і для SimaBEVPool із входами BF16;
depth_scale * context_scale / quantization.multiplier для SimaBEVPool, обидва входи INT8
якого мають масштаб; квантування ознак для SimaPillarScatter. В інших випадках квантування
не публікується.
Для входів INT8 у Scatter поканальне квантування підтримується лише на останній
осі ознак C, зокрема для форм на зразок [1,M,1,C]. Задайте C скінченних
додатних масштабів і C нульових точок INT8, кожна з яких дорівнює параметру
Scatter zero_point для заповнення порожніх комірок. Полотно зберігає ці значення,
а вісь квантування змінюється на 3 у [1,H,W,C]. Інші осі квантування
відхиляються: параметри осі стовпців не можуть описувати фіксоване полотно після
розподілу стовпців за координатами.
SimaBEVPool не підтримує метадані поканального квантування й відхиляє їх,
а не мовчки видаляє. Поведінка наявних потензорних метаданих і явно заданих
нульових точок не змінюється; відсутні вхідні метадані залишаються невідомими.
Тензори BF16
- SimaVoxelize з
output_dtype = TensorDType::BFloat16округлює кожне значення декорації binary32 до найближчого парного BF16, зберігає кожне NaN як0x7FC0і заповнює порожні слоти значенням +0.0. Залиштеquantizationтиповим; будь-яке інше значення відхиляється. - SimaPillarScatter зберігає тип ознак. Для BF16 потрібен
zero_point = 0, тому порожні комірки містять +0.0. Кожна комірка полотна BF16 зберігає молодші байтиCканалів, а потім старші.semantic.tessявно описує це фізичне розміщення:slice_shape = [1,1,1,C],axes = [0,1,2,3],format = "MLA_TILED_BF16_COMPACT";storage_bytes— перевірений розмір даних. Плитки D/H/W/C впорядковано за C/D/H/W, дані всередині — за D/H/W/C. BF16 використовує окремі молодші й старші байти кожної комірки; плитки вирівняно до 16 байтів.copy_dense_bytes_tight(), типізованийdata_ptr()і DLPack відхиляють це нещільне розміщення.copy_payload_bytes()читає лише визначений діапазон збережених байтів.clone()і вихід Owned зберігають байти та метадані без перетворення плиток. Наступні вузли, які розуміють таке розміщення, наприкладSimaArgMax, безпосередньо отримують перевірений опис виробника в тому самому графі. Споживачі лише щільних даних і непідтримувані плиткові входи графа його відхиляють. Повноканальні плитки-комірки INT8 побайтово тотожні щільному NHWC, тому звичайне щільне копіювання та DLPack працюють як раніше. Давні позначкиTessSpecлише з форматом не змінюють поведінки; ненульовеstorage_bytesозначає явне плиткове сховище. - SimaBEVPoolPlan приймає
input_dtype— тип входівSimaBEVPool. ПланV2розподіляє кеші EV74 у байтах, тож план BF16 може бути більшим. ПланV1однаковий для обох типів. - SimaBEVPool приймає
depthіcontextу BF16 з нульовими точками 0. Сума кожної комірки обчислюється в binary32 у порядку точок плану, тож планиV1іV2можуть дати різні байти BF16.bevу BF16 зберігає кожну суму, округлену до найближчого парного, з NaN як0x7FC0, і потребує типовогоquantization.bevу INT8 із входів BF16 квантує суму: задайтеquantization.multiplierрівним1 / output_scale. Входи INT8 не можуть датиbevу BF16.
Додані поля TessSpec::axes і TessSpec::storage_bytes мають типові значення, тому наявні агрегатні ініціалізатори з двома полями зберігають сумісність вихідного коду. Бінарне компонування публічної структури змінюється: перебудуйте споживачів C++ і прив’язки Python з узгодженими заголовками та бібліотеками Core; не використовуйте повторно бінарні файли, зібрані з попереднім компонуванням.
Під’єднайте вузли до конвеєра
Ці вузли — звичай ні етапи графа, але їхні входи надходять від кількох попередніх етапів:
- У PointPillars
SimaPillarScatterпотребує ознак стовпців від моделі ознак стовпців разом ізcoordinatesіcountвідSimaVoxelize. - У BEVFusion
SimaBEVPoolпотребуєdepthіcontextвід моделі камер разом із планом відSimaBEVPoolPlan.
Об’єднайте ці тензори в один вхідний зразок у наведеному вище порядку перед вузлом. Наприклад, отримайте обидва результати у своєму застосунку й надішліть їх разом до графа вузла.
SimaPillarScatter копіює байти ознак без повторного квантування, тож ознаки вже мають
використовувати вхідне квантування моделі, що читає полотно. SimaBEVPool отримує всі камери в
одному тензорі [N,...].
Плани та стан
План залежить лише від калібрування та SimaBEVPoolPlanOptions. Із cache_plan = true
кадр, байти калібрування якого збігаються з попереднім кадром, копіює план попереднього кадру
замість того, щоб обчислювати його знову. Кеш зберігається в контексті бібліотеки ядер, яким
середовище виконання володіє для кожного кадру в обробці, тому влучання можливе, але не
гарантоване, коли одночасно обробляються кілька кадрів. Байти плану однакові в обох
випадках.
SimaBEVPool не зберігає стану між кадрами. Під час кожного виконання він перевіряє заголовок і
вміст плану та відхиляє план, що не відповідає його формам.
Обмеження цілей
EV74 додає обмеження до контракту A65:
SimaBEVPoolпотребуєcontextіз розміщенням «канали останніми» та кількістю каналів, кратною 16, іbevіз розміщенням «канали останніми». ПланV2також потребуєdepthіз розміщенням «канали останніми».SimaBEVPoolPlanвиконується лише на A65.
Запит, який ціль не підтримує, завершується помилкою на цій цілі. Вузол не вибирає замість вас іншу ціль.