Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 67 additions & 61 deletions engine/pdnet.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#define _POSIX_C_SOURCE 200809L
#include "pdnet.h"

#include <math.h>
Expand All @@ -18,16 +17,18 @@
#define MAX_LEVELS 3
#define NMS_IOU 0.55f

#ifdef PDNET_PROFILE
#include <time.h>
double pdnet_prof_us[MAX_OPS];
void pdnet_profile_dump(const PdNet *n, int runs);
#endif
/* A blob's activations, all tensors together, may not ask for more than
* this: the real network's are a few hundred KB, and a camera has a
* hundred-odd MB for everything. */
#define MAX_ACTIVATION_BYTES (8u << 20)

enum { OP_CONV = 1, OP_RESIZE2X = 2, OP_ADD = 3 };

enum { T_NONE = 0, T_INT8, T_FLOAT };

typedef struct {
int h, w, c;
int kind; /* T_INT8 or T_FLOAT once something produces it */
int8_t *q; /* int8 activation, HWC */
float *f; /* float head output, HWC */
} Tensor;
Expand Down Expand Up @@ -115,6 +116,21 @@ static float f32(Rd *r) {

static bool tensor_ok(const PdNet *n, uint32_t i) { return i < (uint32_t)n->n_t; }

static bool zp_ok(int32_t zp) { return zp >= -128 && zp <= 127; }

/* What requant() can shift by without undefined behaviour. */
static bool shift_ok(int32_t shift) { return shift >= -31 && shift <= 30; }

/* An op reads only tensors something earlier produced as int8, and writes a
* tensor nothing has written yet: the order is the execution order, and every
* buffer is read as what it is. */
static bool reads_int8(const PdNet *n, uint32_t i) {
return tensor_ok(n, i) && n->t[i].kind == T_INT8;
}
static bool writes_new(const PdNet *n, uint32_t i) {
return tensor_ok(n, i) && n->t[i].kind == T_NONE;
}

static bool parse(PdNet *n, Rd *r) {
const void *m = take(r, 8);
if (!m || memcmp(m, PDNET_MAGIC, 8) || u32(r) != 1)
Expand All @@ -123,8 +139,9 @@ static bool parse(PdNet *n, Rd *r) {
n->in_h = (int)u32(r);
const float in_scale = f32(r);
const int32_t in_zp = i32(r);
/* Even: NV21 carries chroma for pixel pairs and row pairs. */
if (r->bad || n->in_w <= 0 || n->in_w > 4096 || n->in_h <= 0 || n->in_h > 4096 ||
!(in_scale > 0))
(n->in_w & 1) || (n->in_h & 1) || !(in_scale > 0) || !zp_ok(in_zp))
return false;
/* The frame's bytes to the network's int8 input, as the float model's
* preprocessing defines it: Y from BT.709 limited range to -1..1, U and
Expand All @@ -139,14 +156,21 @@ static bool parse(PdNet *n, Rd *r) {
n->n_t = (int)u32(r);
if (r->bad || n->n_t <= 0 || n->n_t > MAX_TENSORS)
return false;
uint64_t total = 0;
for (int i = 0; i < n->n_t; i++) {
n->t[i].h = (int)u32(r);
n->t[i].w = (int)u32(r);
n->t[i].c = (int)u32(r);
if (r->bad || n->t[i].h <= 0 || n->t[i].h > 4096 || n->t[i].w <= 0 ||
n->t[i].w > 4096 || n->t[i].c <= 0 || n->t[i].c > 4096)
return false;
/* Each dimension bounded is not enough: their product is 2^36. Count
* every tensor as float, the larger of its two possible storages. */
total += (uint64_t)n->t[i].h * n->t[i].w * n->t[i].c * sizeof(float);
if (total > MAX_ACTIVATION_BYTES)
return false;
}
n->t[0].kind = T_INT8; /* the input, which pdnet_detect() fills */
n->n_ops = (int)u32(r);
if (r->bad || n->n_ops <= 0 || n->n_ops > MAX_OPS)
return false;
Expand All @@ -158,15 +182,19 @@ static bool parse(PdNet *n, Rd *r) {
o->out = (int)u32(r);
o->k = (int)u32(r);
o->stride = (int)u32(r);
o->pad = (int)u32(r);
const uint32_t pad = u32(r);
o->pad = pad < 8 ? (int)pad : 8;
o->dw = (int)u32(r);
o->cin = (int)u32(r);
o->cout = (int)u32(r);
o->float_out = (int)u32(r);
o->in_zp = i32(r);
o->out_zp = i32(r);
if (r->bad || !tensor_ok(n, o->in) || !tensor_ok(n, o->out) || o->k < 1 ||
o->k > 7 || o->stride < 1 || o->stride > 4 || o->pad > o->k)
/* A 1x1 is a plain per-pixel multiply: no stride, no padding. */
if (r->bad || !reads_int8(n, o->in) || !writes_new(n, o->out) || o->k < 1 ||
o->k > 7 || o->stride < 1 || o->stride > 4 || o->pad >= o->k ||
(o->k == 1 && o->stride != 1) || !zp_ok(o->in_zp) || !zp_ok(o->out_zp) ||
(o->float_out != 0 && o->float_out != 1) || (o->dw != 0 && o->dw != 1))
return false;
const Tensor *ti = &n->t[o->in], *to = &n->t[o->out];
if (ti->c != o->cin || to->c != o->cout || (o->dw && o->cin != o->cout) ||
Expand All @@ -185,19 +213,19 @@ static bool parse(PdNet *n, Rd *r) {
if (r->bad)
return false;
if (!o->float_out)
for (int c = 0; c < o->cout; c++) {
const int32_t sh = o->ms[2 * c + 1];
if (sh > 30 || sh < -31)
for (int c = 0; c < o->cout; c++)
if (!shift_ok(o->ms[2 * c + 1]))
return false;
}
n->t[o->out].kind = o->float_out ? T_FLOAT : T_INT8;
} else if (o->type == OP_RESIZE2X) {
o->in = (int)u32(r);
o->out = (int)u32(r);
if (r->bad || !tensor_ok(n, o->in) || !tensor_ok(n, o->out))
if (r->bad || !reads_int8(n, o->in) || !writes_new(n, o->out))
return false;
const Tensor *ti = &n->t[o->in], *to = &n->t[o->out];
if (to->h != 2 * ti->h || to->w != 2 * ti->w || to->c != ti->c)
return false;
n->t[o->out].kind = T_INT8;
} else if (o->type == OP_ADD) {
o->in = (int)u32(r);
o->in2 = (int)u32(r);
Expand All @@ -206,13 +234,16 @@ static bool parse(PdNet *n, Rd *r) {
o->m_a = i32(r); o->s_a = i32(r);
o->m_b = i32(r); o->s_b = i32(r);
o->m_o = i32(r); o->s_o = i32(r);
if (r->bad || !tensor_ok(n, o->in) || !tensor_ok(n, o->in2) ||
!tensor_ok(n, o->out))
if (r->bad || !reads_int8(n, o->in) || !reads_int8(n, o->in2) ||
!writes_new(n, o->out) || !zp_ok(o->zp_a) || !zp_ok(o->zp_b) ||
!zp_ok(o->zp_o) || !shift_ok(o->s_a) || !shift_ok(o->s_b) ||
!shift_ok(o->s_o))
return false;
const Tensor *a = &n->t[o->in], *b = &n->t[o->in2], *to = &n->t[o->out];
if (a->h != b->h || a->w != b->w || a->c != b->c || to->h != a->h ||
to->w != a->w || to->c != a->c)
return false;
n->t[o->out].kind = T_INT8;
} else {
return false;
}
Expand All @@ -224,8 +255,13 @@ static bool parse(PdNet *n, Rd *r) {
n->lv[i].stride = (int)u32(r);
n->lv[i].cls = (int)u32(r);
n->lv[i].reg = (int)u32(r);
if (r->bad || !tensor_ok(n, n->lv[i].cls) || !tensor_ok(n, n->lv[i].reg) ||
n->t[n->lv[i].cls].c != 1 || n->t[n->lv[i].reg].c != 4)
if (r->bad || !tensor_ok(n, n->lv[i].cls) || !tensor_ok(n, n->lv[i].reg))
return false;
/* Both float, as the decode reads them, on the same grid. */
const Tensor *tc = &n->t[n->lv[i].cls], *tr = &n->t[n->lv[i].reg];
const int st = n->lv[i].stride;
if (tc->kind != T_FLOAT || tr->kind != T_FLOAT || tc->c != 1 || tr->c != 4 ||
tc->h != tr->h || tc->w != tr->w || st < 1 || st > 64)
return false;
}
return !r->bad && n->t[0].h == n->in_h && n->t[0].w == n->in_w && n->t[0].c == 3;
Expand Down Expand Up @@ -286,14 +322,10 @@ PdNet *pdnet_open(const char *path) {
bool ok = true;
for (int i = 0; ok && i < n->n_t; i++) {
const size_t e = (size_t)n->t[i].h * n->t[i].w * n->t[i].c;
bool is_float = false;
for (int j = 0; j < n->n_ops; j++)
if (n->op[j].type == OP_CONV && n->op[j].out == i && n->op[j].float_out)
is_float = true;
if (is_float)
if (n->t[i].kind == T_FLOAT)
ok = (n->t[i].f = malloc(e * sizeof(float))) != NULL;
else
ok = (n->t[i].q = malloc(e)) != NULL;
ok = (n->t[i].q = calloc(e, 1)) != NULL;
}
int max_c = 0;
for (int j = 0; j < n->n_ops; j++)
Expand Down Expand Up @@ -727,15 +759,9 @@ int pdnet_detect(
p[3 * i + 2] = n->lut_c[cr[ci]]; /* V */
}
}
#ifdef PDNET_PROFILE
struct timespec t0, t1;
#endif
for (int j = 0; j < n->n_ops; j++) {
const Op *o = &n->op[j];
Tensor *to = &n->t[o->out];
#ifdef PDNET_PROFILE
clock_gettime(CLOCK_MONOTONIC, &t0);
#endif
if (o->type == OP_CONV) {
const Tensor *ti = &n->t[o->in];
#ifdef PDNET_NEON
Expand All @@ -762,10 +788,6 @@ int pdnet_detect(
} else {
add(o, &n->t[o->in], &n->t[o->in2], to);
}
#ifdef PDNET_PROFILE
clock_gettime(CLOCK_MONOTONIC, &t1);
pdnet_prof_us[j] += (t1.tv_sec - t0.tv_sec) * 1e6 + (t1.tv_nsec - t0.tv_nsec) / 1e3;
#endif
}

/* Decode as the TVM shim did: an anchor-free head, a box per cell whose
Expand All @@ -781,12 +803,17 @@ int pdnet_detect(
const int s = (int)(sigmoidf(tc->f[i]) * 100.0f + 0.5f);
if (s < score_min || nc == (int)(sizeof(cand) / sizeof(cand[0])))
continue;
const float *r = tr->f + (size_t)i * 4;
/* Distances bounded before they become ints, and only there:
* at a stride of at most 64, 1e6 strides keeps every edge
* inside an int, and no box that fits one is changed. */
float r[4];
for (int s4 = 0; s4 < 4; s4++)
r[s4] = fminf(fmaxf(0.0f, tr->f[(size_t)i * 4 + s4]), 1e6f);
const float ccx = (cx + 0.5f) * st, ccy = (cy + 0.5f) * st;
const int x0 = (int)(ccx - fmaxf(0.0f, r[0]) * st);
const int y0 = (int)(ccy - fmaxf(0.0f, r[1]) * st);
const int x1 = (int)(ccx + fmaxf(0.0f, r[2]) * st);
const int y1 = (int)(ccy + fmaxf(0.0f, r[3]) * st);
const int x0 = (int)(ccx - r[0] * st);
const int y0 = (int)(ccy - r[1] * st);
const int x1 = (int)(ccx + r[2] * st);
const int y1 = (int)(ccy + r[3] * st);
if (x1 <= x0 || y1 <= y0)
continue;
cand[nc++] = (PdBox){x0, y0, x1 - x0, y1 - y0, s};
Expand All @@ -813,24 +840,3 @@ const float *pdnet_head(const PdNet *n, int level, int reg, int *h, int *w) {
return t->f;
}

#ifdef PDNET_PROFILE
void pdnet_profile_dump(const PdNet *n, int runs) {
double total = 0;
for (int j = 0; j < n->n_ops; j++)
total += pdnet_prof_us[j];
for (int j = 0; j < n->n_ops; j++) {
const Op *o = &n->op[j];
const Tensor *to = &n->t[o->out];
const char *kind = o->type == OP_RESIZE2X ? "resize" : o->type == OP_ADD ? "add"
: o->dw ? "dw" : o->k == 1 ? "1x1" : "kxk";
long macs = 0;
if (o->type == OP_CONV)
macs = (long)to->h * to->w * o->cout * o->k * o->k * (o->dw ? 1 : o->cin);
fprintf(stderr, "op %2d %-6s k%d s%d %3d->%3d out %3dx%3d %7.2f ms %5.1f%% %6.1f MMAC/s\n",
j, kind, o->k, o->stride, o->cin, o->cout, to->h, to->w,
pdnet_prof_us[j] / runs / 1e3, 100 * pdnet_prof_us[j] / total,
macs ? macs / (pdnet_prof_us[j] / runs) : 0.0);
}
fprintf(stderr, "total %.2f ms per run\n", total / runs / 1e3);
}
#endif
Loading