NOTE: The Jupyter Notebook below is included in the Chimera SDK and can be run interactively by running the following CLI command:
$ quadric sdk notebook
From the Jupyter Notebook window in your browser, select the notebook named /quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_chipy.ipynb.
YOLOX End-to-End on Chimera GPNPU
This notebook runs the entire YOLOX detection pipeline on the Chimera GPNPU — backbone inference, box decoding, and NMS — in a single compiled program.
Why end-to-end?
Most detection demos split the pipeline: the backbone runs on the accelerator while postprocessing (decode + NMS) runs on the host CPU. This creates a round-trip penalty and leaves the CPU as a bottleneck.
Here we use Chipy to compose the CGC-compiled backbone with a CCL custom op that handles decode + NMS entirely on-chip. The result is a single GPNPU program with zero host intervention between backbone and output.
Pipeline
One switch, two models. Flip a single MODEL line to run either variant — the pipeline and the CCL kernel are identical; only the input resolution, FPN grid sizes, and OCM budget follow along.
| Model | Input | FPN grids | Proposals | Target |
|---|---|---|---|---|
| YOLOX-M | 640×640 | 80/40/20 | 8400 | QC-N, 8 MB OCM |
| YOLOX-Tiny | 416×416 | 52/26/13 | 3549 | QC-N, 4 MB OCM |
1. Setup
import numpy as np
from tvm.contrib.epu import chipy
from yolox_e2e_helpers import (
YOLOX_VARIANTS,
compare_detections,
cpu_decode_and_nms,
download_model,
load_input,
print_detections,
)
%matplotlib inline
2. Select Model
This is the switch. Choose a variant below. Both are stock models, cut at the 3 FPN heads and quantized to asymmetric int8 with the identical pipeline — they drop straight in. Everything downstream (input size, grids, OCM budget) is read from the variant config.
## ════════════════════════════════════════════════════════════════════════
## THE SWITCH — pick a model. Everything else follows automatically.
## ════════════════════════════════════════════════════════════════════════
MODEL = "yolox-tiny" # "yolox-tiny" (416, QC-N 4 MB) | "yolox-m" (640, QC-N 8 MB)
## ════════════════════════════════════════════════════════════════════════
CFG = YOLOX_VARIANTS[MODEL]
IMAGE_SIZE = CFG["image_size"]
print(f"{CFG['display']} · {IMAGE_SIZE}×{IMAGE_SIZE} · {CFG['params']}")
print(f"Target: QC-N · {CFG['ocm_size']} OCM")
model_path, tranges_path = download_model(MODEL)
YOLOX-Tiny · 416×416 · 5.06M params, 6.45 GFLOPs
Target: QC-N · 4MB OCM
Downloading yolox-tiny-noconcat_OpSet16_optimized_asym_int8_q_shaped.onnx ...
Model: yolox-tiny-noconcat_OpSet16_optimized_asym_int8_q_shaped.onnx (5.3 MB)
Downloading yolox-tiny-noconcat_OpSet16_optimized_asym_int8_q_shaped.tranges ...
Tranges: yolox-tiny-noconcat_OpSet16_optimized_asym_int8_q_shaped.tranges
3. The CCL Postprocessing Kernel
The decode + NMS pipeline is implemented as a single CCL kernel in yolox_postprocess.hpp.
| Stage | What happens |
|---|---|
| Split DMA | Load box+obj [1,5,H,W] and classes [1,80,H,W] separately via channel-offset memCpy |
| Flow argmax | reduce<Direction::Channel> over 80 class channels → best class index via ReadFlow/WriteFlow |
| Decode | 7 RAU loads/proposal: 5 box+obj (batched via rau::load::tiles) + 1 argmax + 1 class value |
| FastDiv | floor(n/d) = (n * ceil(2^22/d)) >> 22 for grid coordinate computation |
| NMS | nn::nms — class-aware, all in OCM |
One kernel, any resolution. The per-scale grid sizes and total proposal count are derived at compile time from the FPN head tensor shapes (NUM_ROWS/NUM_COLS) — so the same kernel serves YOLOX-M (80/40/20 → 8400) and YOLOX-Tiny (52/26/13 → 3549) with no edits. Only the FPN strides (8/16/32) are fixed by the architecture. All decoded proposals fit in OCM (~100 KB for boxes + scores + classes in FP16); each FPN head is split-loaded, reduced, decoded, then freed before the next.
## Show key patterns from the CCL kernel
from pathlib import Path
hpp = Path("yolox_postprocess.hpp").read_text()
## Show the ArgmaxLambda functor
print("// === Flow-based argmax over 80 class channels ===")
f_start = hpp.find("template <typename ElemT, std::int32_t numClasses>")
f_end = hpp.find("};", f_start) + 2
print(hpp[f_start:f_end].strip())
print("\n// === Per-scale processing: DMA → argmax → decode ===")
s_start = hpp.find("// Split DMA: channels 0-4")
s_end = hpp.find("ocmMem.free(ocmBoxObj);", s_start)
s_end = hpp.find("\n", s_end) + 1
print(hpp[s_start:s_end].strip())
print("\n// ... [decode reads 7 values/proposal via RAU, then NMS + DMA out]")
// === Flow-based argmax over 80 class channels ===
template <typename ElemT, std::int32_t numClasses>
struct ArgmaxLambda {
INLINE void operator()(
container::NDArray<qVar_t<ElemT>, numClasses>& cls,
qVar_t<std::int16_t>& result)
{
qVar_t<ElemT> maxVal = cls[0];
qVar_t<std::int16_t> maxIdx = 0;
for (std::int32_t i = 1; i < numClasses; i++) {
auto better = cls[i] > maxVal;
maxVal = better ? cls[i] : maxVal;
maxIdx = better ? static_cast<qVar_t<std::int16_t>>(i) : maxIdx;
}
result = maxIdx;
}
};
// === Per-scale processing: DMA → argmax → decode ===
// Split DMA: channels 0-4 (box+obj) and channels 5-84 (classes)
memCpy(ddrHead, ocmBoxObj);
memCpy(ddrHead, ocmCls, 0, 5, 0, 0);
// Flow-based argmax: 80 class channels → 1 class index per position
ArgmaxLambda<HeadElemT, YOLOX_NUM_CLASSES> argmaxLambda;
reduce<Direction::Channel>(ocmCls, ocmArgmax, argmaxLambda);
yoloxDecodeScale<gridH, gridW, stride, propOffset>(
ocmBoxObj, ocmCls, ocmArgmax, ocmBoxes, ocmScores, ocmClasses);
ocmMem.free(ocmArgmax);
ocmMem.free(ocmCls);
ocmMem.free(ocmBoxObj);
// ... [decode reads 7 values/proposal via RAU, then NMS + DMA out]
4. Chipy End-to-End Pipeline
Chipy lets us compose the CGC-compiled backbone with our CCL custom op. The @chipy.ccl_custom_op decorator bridges the C++ kernel, and @chipy.func wires the full pipeline in 5 lines.
@chipy.ccl_custom_op(
ccl_func_name="yoloxPostprocess",
frac_bits=[4, 15, 0], # boxes Q11.4 (BoxFP), scores Q0.15 (ScoreFP16), classes int
reserved_l2m=CFG["reserved_l2m"], # OCM for heads + decoded buffers + NMS
reserved_ext=0,
io_in_ext_mem=True, # inputs/outputs live in DDR
)
def yolox_postprocess(head0, head1, head2, iou_threshold, score_threshold):
"""CPU reference — Chipy calls this in CPU mode, the HPP on GPNPU."""
return cpu_decode_and_nms([head0, head1, head2], iou_threshold, score_threshold)
@chipy.func()
def yolox_e2e(model_path, inp, iou_threshold, score_threshold):
"""YOLOX end-to-end: backbone (ONNX) → decode + NMS (CCL)."""
heads = chipy.infer_onnx(model_path, inp)
return yolox_postprocess(heads[0], heads[1], heads[2], iou_threshold, score_threshold)
print("Pipeline defined: backbone → decode → NMS")
Pipeline defined: backbone → decode → NMS
5. Compile & Run
We run the pipeline twice:
- CPU — ORT backbone + numpy postprocess (float reference)
- GPNPU ISS — Fully compiled (CGC backbone + CCL postprocess), with region profiling
We set the TVM logger to INFO to suppress verbose debug output during compilation.
## Load input and run CPU reference
chipy_inp, img, tranges = load_input(
"../../../common/calibration/coco-like/33887522274_eebd074106_k.jpeg",
tranges_path,
image_size=IMAGE_SIZE,
)
## Normalize value_proto_ranges to the nested [[min, max]] shape that
## CompiledChiPyModule.run expects (workaround for tvm#3261).
tranges = {k: ([v] if v and not isinstance(v[0], list) else v) for k, v in tranges.items()}
IOU_THR, SCORE_THR = 0.45, 0.3
out_cpu = yolox_e2e(str(model_path), chipy_inp, IOU_THR, SCORE_THR)
cpu_boxes, cpu_scores, cpu_classes = (
out_cpu[0].numpy(),
out_cpu[1].numpy(),
out_cpu[2].numpy(),
)
n_cpu = int(np.count_nonzero(cpu_scores > 0))
print_detections("CPU", cpu_boxes, cpu_scores, cpu_classes, n_cpu)
CPU: 7 detections
bus score=0.896 box=[191, 172, 261, 306]
car score=0.852 box=[87, 255, 149, 323]
person score=0.831 box=[349, 247, 366, 306]
person score=0.784 box=[323, 246, 343, 303]
person score=0.451 box=[335, 245, 351, 304]
truck score=0.415 box=[90, 252, 148, 321]
car score=0.321 box=[143, 265, 153, 276]
import logging
from pathlib import Path
from tvm.contrib.epu.chimera_job.hw_config import HWConfig
from tvm.relay.backend.contrib.epu import util as epu_util
from tvm.relay.backend.contrib.epu.util import logger as tvm_logger
tvm_logger.setLevel(logging.INFO)
module_name = f"yolox_e2e_{MODEL.replace('-', '_')}"
hw_config = HWConfig(product="QC-N", ocm_size=CFG["ocm_size"], macs_per_pe=16)
## Pass-context attrs are inherited from the outer `with` block into
## compile_and_run's internal target + pass_ctx. Alongside profiling
## (output_profile / enable_region_profile) we turn on the compiler's
## optimization passes: enhanced conv fusion, prefetching, QLUT, extra
## aggressive sinking, advanced directives, and LRM-overflow checking.
with epu_util.add_epu_passcontext_attributes(
output_profile=True,
enable_region_profile=True,
enhanced_conv_fusion=True,
enable_prefetching=True,
enable_qlut=True,
enable_extra_aggressive_sinking=True,
advanced_directives=True,
check_lrm_overflow=True,
):
out_gpnpu = yolox_e2e.compile_and_run(
hw_config=hw_config,
module_name=module_name,
custom_op_header=str(Path(".").resolve() / "yolox_postprocess.hpp"),
value_proto_ranges=tranges,
model_path=str(model_path),
inp=chipy_inp,
iou_threshold=IOU_THR,
score_threshold=SCORE_THR,
)
/quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_helpers.py:197: RuntimeWarning: overflow encountered in multiply
decoded[:, 0:2] = (decoded[:, 0:2] + grids) * exp_strides
/quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_helpers.py:198: RuntimeWarning: overflow encountered in exp
decoded[:, 2:4] = np.exp(decoded[:, 2:4]) * exp_strides
/quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_helpers.py:206: RuntimeWarning: invalid value encountered in add
boxes_xyxy[:, 3] = boxes_cxcywh[:, 1] + boxes_cxcywh[:, 3] / 2
/quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_helpers.py:208: RuntimeWarning: overflow encountered in multiply
combined = decoded[:, 4, None] * decoded[:, 5:] # obj * cls
/quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_helpers.py:208: RuntimeWarning: invalid value encountered in multiply
combined = decoded[:, 4, None] * decoded[:, 5:] # obj * cls
/quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_helpers.py:158: RuntimeWarning: invalid value encountered in subtract
inter = np.maximum(0, xx2 - xx1) * np.maximum(0, yy2 - yy1)
/quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_helpers.py:158: RuntimeWarning: invalid value encountered in multiply
inter = np.maximum(0, xx2 - xx1) * np.maximum(0, yy2 - yy1)
/quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_helpers.py:159: RuntimeWarning: invalid value encountered in scalar multiply
a_i = (boxes[idx, 2] - boxes[idx, 0]) * (boxes[idx, 3] - boxes[idx, 1])
/quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_helpers.py:160: RuntimeWarning: invalid value encountered in subtract
a_j = (boxes[order[1:], 2] - boxes[order[1:], 0]) * (
/quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_helpers.py:161: RuntimeWarning: invalid value encountered in subtract
boxes[order[1:], 3] - boxes[order[1:], 1]
/quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_helpers.py:160: RuntimeWarning: invalid value encountered in multiply
a_j = (boxes[order[1:], 2] - boxes[order[1:], 0]) * (
/quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_helpers.py:163: RuntimeWarning: invalid value encountered in subtract
iou = inter / (a_i + a_j - inter + 1e-6)
/quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_helpers.py:158: RuntimeWarning: overflow encountered in subtract
inter = np.maximum(0, xx2 - xx1) * np.maximum(0, yy2 - yy1)
/quadric/sdk-cli/examples/models/yolo/yolox_e2e/yolox_e2e_helpers.py:159: RuntimeWarning: invalid value encountered in scalar subtract
a_i = (boxes[idx, 2] - boxes[idx, 0]) * (boxes[idx, 3] - boxes[idx, 1])
2026-07-18 12:09 - INFO - epu - codegen - START==================================build_relay
2026-07-18 12:09 - INFO - epu - codegen - START===============================optimize_relay
2026-07-18 12:09 - INFO - epu - codegen - START====================quantize_to_cpu_runnable_fx
2026-07-18 12:09 - INFO - epu - fx -
Source name Op Output 0 Range Output 0 Frac Bits Output 1 Range Output 1 Frac Bits Output 2 Range Output 2 Frac Bits
---------------------------------------- ----------------------------- ---------------------------------------- -------------------- ---------------- -------------------- ---------------- --------------------
450 strided_slice (0.0, 255.0) 23 N/A N/A N/A N/A
452 strided_slice (0.0, 255.0) 23 N/A N/A N/A N/A
451 strided_slice (0.0, 255.0) 23 N/A N/A N/A N/A
453 strided_slice (0.0, 255.0) 23 N/A N/A N/A N/A
/head/Concat_output_0_DequantizeLinear contrib.epu.dequantize (-2.277541358023882, 4.104580029845238) 28 N/A N/A N/A N/A
/head/Concat_1_output_0_DequantizeLinear contrib.epu.dequantize (-2.5013048574328423, 3.459748774766922) 28 N/A N/A N/A N/A
/head/Concat_2_output_0_DequantizeLinear contrib.epu.dequantize (-1.8887041509151459, 3.025781139731407) 28 N/A N/A N/A N/A
contrib.epu.quadric_custom_op (-134217728.0, 134217727.9375) 4 N/A 15 N/A 0
2026-07-18 12:09 - INFO - epu - codegen - START====================build_cpu_runnable_fx_relay
2026-07-18 12:09 - INFO - epu - codegen - START=======================quantize_to_chimera_fx
2026-07-18 12:09 - INFO - epu - codegen - START=================================relay_to_tir
2026-07-18 12:09 - INFO - epu - codegen - START===========================relay_to_epu_relay
2026-07-18 12:09 - INFO - epu - codegen - START==============================adapt_and_order
2026-07-18 12:09 - INFO - epu - mac_counter -
2026-07-18 12:09 - INFO - epu - mac_counter - ============================================================
2026-07-18 12:09 - INFO - epu - mac_counter - MAC Operation Count Summary
2026-07-18 12:09 - INFO - epu - mac_counter - ============================================================
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 224,280,576 ops (112,140,288 MACs) - /backbone/backbone/stem/conv/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 224,280,576 ops (112,140,288 MACs) - /backbone/backbone/dark2/dark2.0/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/backbone/dark2/dark2.1/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 12,460,032 ops (6,230,016 MACs) - /backbone/backbone/dark2/dark2.1/m/m.0/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /backbone/backbone/dark2/dark2.1/m/m.0/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/backbone/dark2/dark2.1/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 49,840,128 ops (24,920,064 MACs) - /backbone/backbone/dark2/dark2.1/conv3/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 224,280,576 ops (112,140,288 MACs) - /backbone/backbone/dark3/dark3.0/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/backbone/dark3/dark3.1/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 12,460,032 ops (6,230,016 MACs) - /backbone/backbone/dark3/dark3.1/m/m.0/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /backbone/backbone/dark3/dark3.1/m/m.0/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 12,460,032 ops (6,230,016 MACs) - /backbone/backbone/dark3/dark3.1/m/m.1/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /backbone/backbone/dark3/dark3.1/m/m.1/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 12,460,032 ops (6,230,016 MACs) - /backbone/backbone/dark3/dark3.1/m/m.2/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /backbone/backbone/dark3/dark3.1/m/m.2/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/backbone/dark3/dark3.1/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 49,840,128 ops (24,920,064 MACs) - /backbone/backbone/dark3/dark3.1/conv3/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 224,280,576 ops (112,140,288 MACs) - /backbone/backbone/dark4/dark4.0/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/backbone/dark4/dark4.1/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 12,460,032 ops (6,230,016 MACs) - /backbone/backbone/dark4/dark4.1/m/m.0/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /backbone/backbone/dark4/dark4.1/m/m.0/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 12,460,032 ops (6,230,016 MACs) - /backbone/backbone/dark4/dark4.1/m/m.1/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /backbone/backbone/dark4/dark4.1/m/m.1/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 12,460,032 ops (6,230,016 MACs) - /backbone/backbone/dark4/dark4.1/m/m.2/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /backbone/backbone/dark4/dark4.1/m/m.2/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/backbone/dark4/dark4.1/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 49,840,128 ops (24,920,064 MACs) - /backbone/backbone/dark4/dark4.1/conv3/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 224,280,576 ops (112,140,288 MACs) - /backbone/backbone/dark5/dark5.0/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/backbone/dark5/dark5.1/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 99,680,256 ops (49,840,128 MACs) - /backbone/backbone/dark5/dark5.1/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/backbone/dark5/dark5.2/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 12,460,032 ops (6,230,016 MACs) - /backbone/backbone/dark5/dark5.2/m/m.0/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /backbone/backbone/dark5/dark5.2/m/m.0/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/backbone/dark5/dark5.2/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 49,840,128 ops (24,920,064 MACs) - /backbone/backbone/dark5/dark5.2/conv3/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/lateral_conv0/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 49,840,128 ops (24,920,064 MACs) - /backbone/C3_p4/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 12,460,032 ops (6,230,016 MACs) - /backbone/C3_p4/m/m.0/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /backbone/C3_p4/m/m.0/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 49,840,128 ops (24,920,064 MACs) - /backbone/C3_p4/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 49,840,128 ops (24,920,064 MACs) - /backbone/C3_p4/conv3/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/reduce_conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 49,840,128 ops (24,920,064 MACs) - /backbone/C3_p3/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 12,460,032 ops (6,230,016 MACs) - /backbone/C3_p3/m/m.0/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /backbone/C3_p3/m/m.0/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 49,840,128 ops (24,920,064 MACs) - /backbone/C3_p3/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 49,840,128 ops (24,920,064 MACs) - /backbone/C3_p3/conv3/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 49,840,128 ops (24,920,064 MACs) - /head/stems.0/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 448,561,152 ops (224,280,576 MACs) - /head/reg_convs.0/reg_convs.0.0/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 448,561,152 ops (224,280,576 MACs) - /head/reg_convs.0/reg_convs.0.1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 2,076,672 ops (1,038,336 MACs) - /head/reg_preds.0/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 519,168 ops (259,584 MACs) - /head/obj_preds.0/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 448,561,152 ops (224,280,576 MACs) - /head/cls_convs.0/cls_convs.0.0/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 448,561,152 ops (224,280,576 MACs) - /head/cls_convs.0/cls_convs.0.1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 41,533,440 ops (20,766,720 MACs) - /head/cls_preds.0/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /backbone/bu_conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/C3_n3/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 12,460,032 ops (6,230,016 MACs) - /backbone/C3_n3/m/m.0/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /backbone/C3_n3/m/m.0/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/C3_n3/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 49,840,128 ops (24,920,064 MACs) - /backbone/C3_n3/conv3/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /head/stems.1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /head/reg_convs.1/reg_convs.1.0/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /head/reg_convs.1/reg_convs.1.1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 519,168 ops (259,584 MACs) - /head/reg_preds.1/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 129,792 ops (64,896 MACs) - /head/obj_preds.1/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /head/cls_convs.1/cls_convs.1.0/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /head/cls_convs.1/cls_convs.1.1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 10,383,360 ops (5,191,680 MACs) - /head/cls_preds.1/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /backbone/bu_conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/C3_n4/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 12,460,032 ops (6,230,016 MACs) - /backbone/C3_n4/m/m.0/conv1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 112,140,288 ops (56,070,144 MACs) - /backbone/C3_n4/m/m.0/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 24,920,064 ops (12,460,032 MACs) - /backbone/C3_n4/conv2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 49,840,128 ops (24,920,064 MACs) - /backbone/C3_n4/conv3/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 12,460,032 ops (6,230,016 MACs) - /head/stems.2/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 28,035,072 ops (14,017,536 MACs) - /head/reg_convs.2/reg_convs.2.0/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 28,035,072 ops (14,017,536 MACs) - /head/reg_convs.2/reg_convs.2.1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 129,792 ops (64,896 MACs) - /head/reg_preds.2/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 32,448 ops (16,224 MACs) - /head/obj_preds.2/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 28,035,072 ops (14,017,536 MACs) - /head/cls_convs.2/cls_convs.2.0/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 28,035,072 ops (14,017,536 MACs) - /head/cls_convs.2/cls_convs.2.1/conv/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - conv2d: 2,595,840 ops (1,297,920 MACs) - /head/cls_preds.2/Conv_quant
2026-07-18 12:09 - INFO - epu - mac_counter - ------------------------------------------------------------
2026-07-18 12:09 - INFO - epu - mac_counter - Total: 6,412,536,000 ops (3,206,268,000 MACs)
2026-07-18 12:09 - INFO - epu - mac_counter - ============================================================
2026-07-18 12:09 - INFO - epu - mac_counter -
2026-07-18 12:10 - INFO - epu - codegen - START==============================amend_ctrl_flow
2026-07-18 12:10 - INFO - epu - codegen - START=============================plan_lrm_virtual
2026-07-18 12:10 - INFO - epu - codegen - START==============================amend_ctrl_flow
2026-07-18 12:10 - INFO - epu - codegen - START===============================lrm_alloc_loop
2026-07-18 12:12 - INFO - epu - codegen - START==============================amend_ctrl_flow
2026-07-18 12:12 - INFO - epu - codegen - START================================lrm_splitting
2026-07-18 12:14 - INFO - epu - codegen - START==============================ext_split_relay
2026-07-18 12:16 - INFO - epu - codegen - START====================================build_tir
2026-07-18 12:17 - INFO - epu - iss_testing - Found tranges for input: <tvm.contrib.epu.interval.Interval object at 0x7933783bd9f0>
2026-07-18 12:17 - INFO - epu - iss_testing - Started Running Graph on Chimera ISS...
FILM 30/30: 100%|███████████████████████████████████████████████████| 30/30 [00:41<00:00, 1.37s/it]
2026-07-18 12:18 - INFO - epu - iss_testing - Done 0:00:42.436318
out_vals = list(out_gpnpu.values())
gpnpu_boxes = out_vals[0].tensor
gpnpu_scores = out_vals[1].tensor
gpnpu_classes = out_vals[2].tensor
n_gpnpu = int(np.count_nonzero(gpnpu_scores > 0))
print_detections("GPNPU", gpnpu_boxes, gpnpu_scores, gpnpu_classes, n_gpnpu)
print(f"\nCPU: {n_cpu} detections | GPNPU: {n_gpnpu} detections")
GPNPU: 7 detections
car score=0.896 box=[86, 255, 148, 323]
bus score=0.896 box=[188, 170, 262, 302]
person score=0.806 box=[349, 246, 366, 306]
person score=0.784 box=[324, 246, 342, 302]
person score=0.464 box=[336, 244, 350, 302]
truck score=0.384 box=[85, 256, 147, 320]
car score=0.338 box=[143, 265, 153, 275]
CPU: 7 detections | GPNPU: 7 detections
6. Visualization
compare_detections(
img,
(cpu_boxes, cpu_scores, cpu_classes, n_cpu),
(gpnpu_boxes, gpnpu_scores, gpnpu_classes, n_gpnpu),
title=f"{CFG['display']} End-to-End: CPU vs Chimera GPNPU",
)
print("Saved: yolox_e2e_detections.png")
Saved: yolox_e2e_detections.png

7. Performance Profile
The ISS emits per-region cycle counts in profile.json. We use the SDK's built-in profiler to summarize execution cycles, stall cycles, and throughput.
import glob
import tvm.relay.backend.contrib.epu.codegen as epu_codegen
from tvm.contrib.epu.chimera_job import core
build_path = epu_codegen.get_module_build_path(module_name)
profile_files = glob.glob(str(build_path / "profile*.json"))
if profile_files:
profile_files.sort()
print(f"Profile: {profile_files[0]}")
core._plot_profile_results(profile_files[0], clock_freq=1.0e9)
else:
print("No profile.json found — ensure ISS run completed.")
Profile: /quadric/sdk-cli/examples/models/yolo/yolox_e2e/ccl_build/yolox_e2e_yolox_tiny/build/profile.json
[SDK-CLI] : TotalCycles: 11,026,666
[SDK-CLI] : Executions/second: 91
compute : ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 3.533M
data_array : ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 1.833M
mac : ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 4.774M
data_ocm : ▇▇▇▇▇▇▇▇ 795.64K
data_external: ▏ 67.964K
Summary
| Models | YOLOX-M (640×640, 8400 props) · YOLOX-Tiny (416×416, 3549 props) — one MODEL switch |
| Pipeline | Backbone (CGC) + Decode + NMS (CCL) — fully on Chimera GPNPU |
| Target | QC-N (8 cores, 16 MACs/PE) — 8 MB OCM for -M, 4 MB for -Tiny |
| Quantization | Asymmetric int8 activations, int8 weights (COCO calibration) |
| Custom Op | yolox_postprocess.hpp — grid sizes derived from head shapes, class-aware NMS |
Postprocess kernel techniques
| Technique | What it does |
|---|---|
| Flow-based argmax | reduce<Direction::Channel> streams 80 class channels through PEs via ReadFlow/WriteFlow |
| Split DMA | Separate memCpy for box+obj (5 ch) and classes (80 ch) with channel offsets |
| Batched RAU tiles | rau::load::tiles / rau::store::tiles for box coordinates |
| FastDiv | Multiply-shift reciprocal for grid coordinate division |
Key takeaways
- Chipy makes it trivial to compose CGC backbones with CCL custom ops — the pipeline is 5 lines of Python
- One switch, two models — the CCL kernel derives grid sizes from the head tensor shapes, so YOLOX-M (640) and YOLOX-Tiny (416) share the exact same kernel; only the model, input size, and OCM budget change
- CCL custom ops can implement complex postprocessing (decode + NMS) entirely in OCM
- YOLOX-Tiny fits QC-N with 4 MB OCM — the smaller backbone leaves room to reserve OCM for on-chip decode + NMS
- End-to-end on GPNPU eliminates the host round-trip between backbone and postprocess
Citation
@article{yolox2021,
title = {YOLOX: Exceeding YOLO Series in 2021},
author = {Ge, Zheng and Liu, Songtao and Wang, Feng and Li, Zeming and Sun, Jian},
journal = {arXiv preprint arXiv:2107.08430},
year = {2021}
}
