Keeping up with Android Android lifecycle management, Telechips

Keeping up with Android Android lifecycle management, Telechips


Android has entered the world of embedded devices starting form mobile phones, spreading to IoT and to TV. The next important member of the Android ecosystem is the car, and this is bringing a set of interesting new partners to the ecosystem. Following this trend RT-RK has started a partnership project with the SoC vendor Telechips, with a goal to develop and evolve an IVI platform based on the Android Automotive OS. The idea of the project is to allow the Telechips’ team to focus on development of the core automotive related technologies for the IVI system, while our teams need to enable efficient and fast integration of the solution in the Android operating system.

Targeted hardware

The targeted hardware is Telechips TCC805x (Dolphin3) Smart Cockpit Solution. TCC805x (Dolphin3) processor family is a 14nm ideal SoC, targeted at IVI/Cockpit and ADAS, built on extensive market knowledge accumulated over a decade of Telechips’ leadership in the IVI/Cockpit processor business. The Telechips’ roadmap is composed of strategic Pin-to-Pin PKG to cover from an entry to a high-level Cockpit. TCC805x is aimed at Automotive Cockpit, Cluster, and the Infotainment system with flexible design based on Arm® Cortex®-A72 Quad core and Arm Cortex-A53 Quad core.

TCC805x provides great performance of a 2D/3D graphic engine for a rich and vivid GUI with Imagination PowerVR Series9XTP Core and supports hardware virtualization for Hypervisor-Less Cockpit (HLC) solution. Not only does TCC805x support multi-display and multi-channel camera input, but it also embodies Image Signal Processing sub-system and MICOM sub-system in support of isolated safety island.

CC805x provides great performance of a 2D/3D graphic engine for a rich and vivid GUI

Scope of work

To prove readiness to follow a strict tempo of the Android evolution, each SoC vendor needs to prove a significant level of platform stability to Google, in an early stage after an official Android release. This happens via presenting Android xTS results with a high pass level (~90%). For this project, the goal is to execute a migration from Android 12 to Android 13. The effort in bullets:

  • Upgrade the framework code from Android 12 to Android 13
  • Migrate the Android kernel from version 5.4 to version 5.10
  • Migrate all necessary Telechips’ drivers
  • Update the drivers to a version compatible with Android 13 (primarily GPU drivers)
  • Implement missing HAL modules on top of the Telechips’ drivers

Timeline

When launching a commercial device with Android, the timelines defined by Google, although demanding, are very reasonable (around 2 years). However, when approaching the matter from the SoC vendor perspective, this effort is on a critical path for the whole ecosystem. The SoC is the first stop after Google that needs to enable the rest of the ecosystem to access the latest Android release.

Android lifecycle management, Telechips - Timeline

The first Android 13 release was rolled out end of November 2022, and now the SoC vendors are under pressure, both from Google and OEMs, to make their chipsets accessible to the rest of the Android ecosystem.
The common expectation is that the first drops of BSP with Android 13 become available in 3-5 months (in case of this project Q1 2023). In general, there are three types of release definitions in this process:

  • Integrated Build – Functional version with features missing, can be buggy, etc.
  • Compatible Build – Build passing the xTS suite, with known issues to add/fix
  • Approved Build – Build passing all needed tests and running with acceptable quality

Phases of execution

This project is broken into phases across different levels of the software architecture:

  1. Upgrading framework layers
  2. Upgrading kernel
  3. Enabling new drivers/features

Most of the effort in the project is focused on proving proper functionality of the 3 lowest layers with the rest of the framework coming directly from the AOSP repositors.

Android framework upgrade

The schedules enforced by Google may seem hard, but it is important to understand that there is a significant effort being invested to make this transition undergo smoothly.
One of the critical aspects is Android Common Kernel backward compatibility guarantees. In practice this means that Google is building framework in a way which maintains backward compatibility with the older versions of the kernel. The table below shows the status for Android 13:

Android lifecycle management, Telechips - Android framework upgrade
This means that that the current framework for Android 13 should be able to run and launch (with limitations for new features) completely on Android 12 Kernel version 5.4. This sums up the project’s first goal – to enable the Telechips’ devices to run Android 13 on the already existing Android 12 kernel version 5.4
Android 13 should be able to run and launch (with limitations for new features) completely on Android 12 Kernel version 5.4

This action point involves:

  • Update of 1132 software repository coming from the Android project
  • Re-apply of the Telechips’ modifications on 20 repositories
  • Integration of the Telechips’ device with Android 13 upgrade
  • Execution of CTS (Compatibility Test Suite) on the created build

Kernel upgrade

Once the previous step provides satisfying results, the next step is to upgrade the kernel version from version 5.4 to version 5.10. This means merging code from two sources: Android and Telechips’ Linux branch.

Kernel upgrade

This step gets validated through execution and the results of VTS (Vendor Test Suite).

New driver implementation

Android 13 brings several new features that require modifications and upgrades on the hardware level integration layer. Here is a short overview of the affected modules:

  • Audio HAL
  • Camera HAL
  • EVS HAL
  • Hardware Composer HAL
  • Power HAL
  • Vehicle HAL

Validation

The metrics for validation of compatibility with the latest release is simple; it is necessary to successfully pass these 3 test suites:

  • VTS – Vendor Test Suite
  • CTS – Compatibility Test Suite
  • STS – Security Test suite

Vendor Test Suite implies extensive testing of the kernel and Hardware abstraction layer (HAL).
This proves the readiness of the core underlaying software components to support all the necessary software features of the targeted Android release (13 in our case). The test suite covers over 350 software modules evaluated using over 500.000 unique automated tests.

Compatibility Test Suite

The main goal of this test suite is to confirm compatibility of the behavior of the tested device with the Android operating system requirements; in other words, that device can run any Android compatible application without an issue.

The CTS includes the following types of test cases:

  • Unit tests – test atomic units of code within the Android platform; e.g. a single class, such as java.util.HashMap
  • Functional tests – test a combination of APIs together in a higher-level use-case
  • Robustness tests – test durability of the system under stress
  • Performance tests – test performance of the system against defined benchmarks, for example rendering frames per second

This test suite covers over 380 software modules, evaluated using over 600.000 unique automated tests.

Security Test Suite

This test suite checks if the devices apply all the latest security patches. Since we are targeting the very latest Android version, zero issues are expected in this run.
This test suite consists of around 600 test.

With the new runner on the board, we executed the model again to generate the performance dump:

executor_runner --model_path 
/sharefs/mv2.pte --inputs /sharefs/dog_input.bin--etdump_path /sharefs/model.etdump

Once the run finished, we pulled the model.etdump file back to our host PC for analysis:

scp root@BoardsIP:/sharefs/model.etdump.

To analyze the data, we utilized ExecuTorch's Inspector APIs, which provide a clean interface for parsing ETRecord and ETDump files. By using Inspector.to_dataframe, we generated an Excel spreadsheet detailing all recorded events, their execution calls, and their exact runtimes.

However, to map these events back to the original Python source code, specifically capturing exact ATen operator names and stack_traces - we needed to generate an ETRecord file during the initial model export phase. This links back profiling details to the original Python source code (including stack traces and module hierarchy).

To implement this by following the official ETRecord Documentation, we created an updated export script, modifying the original section in export.py from:

prog = export_to_exec_prog(
    model,
    example_inputs,
    dynamic_shapes=dynamic_shapes,
    backend_config=backend_config,
    strict=args.strict,
)

...to the following implementation:

m = model.eval()
m = export(m, example_inputs, strict=True).module()

core_aten_ep = _to_core_aten(
    m,
    example_inputs,
    strict=args.strict,
)

edge_manager = _core_aten_to_edge(
    core_aten_ep,
    edge_compile_config=EdgeCompileConfig(_check_ir_validity=False),
)

edge_manager_copy = copy.deepcopy(edge_manager)
prog = edge_manager.to_executorch(config=backend_config)
generate_etrecord("mv2.etrecord", edge_manager_copy, prog)

We then ran this modified export script to generate both the .pte model and its corresponding mv2.etrecord file:

(.venv) ubuntu@ubuntu:~/executorch$ python3 -m examples.portable.scripts.exportEtRecord --model_name="mv2"
(.venv) ubuntu@ubuntu:~/executorch$ ls -la mv2.etrecord mv2.pte
-rw-r--r-- 1 user nisusers 15509467 May 6 11:52 mv2.etrecord
-rw-r--r-- 1 user nisusers 14233120 May 6 11:52 mv2.pte
(.venv) ubuntu@ubuntu:~/executorch$ scp -v mv2.pte root@BoardsIP:/sharefs

After repeating the inference on the board and pulling the new model.etdump, we loaded both files into the Inspector API:

inspector = Inspector(etdump_path="/path_to/model.etdump", etrecord="/path_to/mv2.etrecord")
df = inspector.to_dataframe()
df.to_csv("data.csv")

The resulting table included full ATen operator names, source stack traces, and module hierarchies. Reviewing this dataframe clearly showed that aten.convolution.default was our slowest operator.

Optimization via RISC-V Vector (RVV) Intrinsics

Our next task was to locate and optimize the underlying source function behind native_call_convolution.out. A thorough search through the ExecuTorch codebase pointed us to the default portable convolution kernel located at ~/executorch/kernels/portable/cpu/op_convolution.cpp.

To accelerate this, we rewrote the intensive parts of the kernel using RISC-V vector intrinsics, creating a new implementation file at /executorch/kernels/portable/cpu/op_convolutionRVV.cpp. We also added a custom .yaml configuration file, according to Kernel Registration Documentation, in /executorch/kernels/portable to register our new kernel:

- op: convolution.out
  kernels:
    - arg_meta: null
      kernel_name: torch::executor::convolutionRVV_out

To guarantee that the build system picked up our optimized kernel instead of the default fallback, we modified ~/praksa/executorch/kernels/portable/CMakeLists.txt to merge our custom configurations:

set(_my_yaml "${CMAKE_CURRENT_SOURCE_DIR}/my_functions.yaml")
set(_yaml "${CMAKE_CURRENT_SOURCE_DIR}/functions.yaml")

merge_yaml(
  FUNCTIONS_YAML ${_my_yaml}
  FALLBACK_YAML ${_yaml}
  OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}
)

gen_selected_ops(
  LIB_NAME "portable_ops_lib"
  OPS_SCHEMA_YAML "${CMAKE_CURRENT_BINARY_DIR}/merged.yaml"
)

generate_bindings_for_kernels(
  LIB_NAME "portable_ops_lib"
  FUNCTIONS_YAML "${CMAKE_CURRENT_BINARY_DIR}/merged.yaml"
)

After rebuilding the runtime, we confirmed that the mappings were correctly bound to aten::convolution.out by checking the generated code files: RegisterCodegenUnboxedKernelsEverything.cpp and NativeFunctions.h inside the build directory.

Performance and Benchmark Comparisons

With the optimizations complete, we transferred the newly compiled executable back to the board and ran a direct benchmark.

msh >executor_runner -model_path /sharefs/mv2.pte -inputs /sharefs/dog_input.bin -etdump_path /sharefs/model.etdump -print_output "none"
--- ExecuTorch Start ---
I 00:00:00.003407 executorch:executor_runner.cpp:276] Loading inputs from input file(s).
I 00:00:00.086602 executorch:executor_runner.cpp:375] Model file /sharefs/mv2.pte is loaded.
I 00:00:00.094620 executorch:executor_runner.cpp:385] Using method forward
I 00:00:00.101219 executorch:executor_runner.cpp:436] Setting up planned buffer 0, size 9936896.
I 00:00:00.115040 executorch:executor_runner.cpp:467] Model loaded in 99.634370 ms.
I 00:00:34.384195 executorch:executor_runner.cpp:525] Iteration 1 of 1: 34261.195795 ms
I 00:00:34.391929 executorch:executor_runner.cpp:535] Model executed successfully 1 time(s) in 34261.195795 ms.
I 00:00:34.401600 executorch:executor_runner.cpp:544] 1 outputs:
I 00:00:34.408598 executorch:executor_runner.cpp:157] ETDump written to file '/sharefs/model.etdump'.

Optimized: RISC-V Vector (RVV) Kernel

msh >executor_runner -model_path /sharefs/mv2.pte -inputs /sharefs/dog_input.bin -etdump_path /sharefs/model.etdump -print_output "none"
--- ExecuTorch Start ---
I 00:00:00.003408 executorch:executor_runner.cpp:276] Loading inputs from input file(s).
I 00:00:00.085513 executorch:executor_runner.cpp:375] Model file /sharefs/mv2.pte is loaded.
I 00:00:00.093531 executorch:executor_runner.cpp:385] Using method forward
I 00:00:00.100130 executorch:executor_runner.cpp:436] Setting up planned buffer 0, size 9936896.
I 00:00:00.113959 executorch:executor_runner.cpp:467] Model loaded in 98.929963 ms.
I 00:00:02.965595 executorch:executor_runner.cpp:525] Iteration 1 of 1: 2843.675211 ms
I 00:00:02.973243 executorch:executor_runner.cpp:535] Model executed successfully 1 time(s) in 2843.675211 ms.
I 00:00:02.982827 executorch:executor_runner.cpp:544] 1 outputs:
I 00:00:02.989899 executorch:executor_runner.cpp:157] ETDump written to file '/sharefs/model.etdump'.

It is important to mention that we used a vector multiplier of LMUL = m4 for our RISC-V vector intrinsic functions. We selected LMUL = m4 because it delivered the best performance on the CanMV-K230 board during testing, where the command:

executor_runner --model_path /sharefs/mv2.pte --inputs
/sharefs/dog_input.bin --etdump_path /sharefs/model.etdump

was executed multiple times using an automated script.

When analyzing the raw 1000-element output tensor, we noticed slight numerical differences between the unoptimized version and the vector version beginning at the 7th or 8th decimal place.

The benchmarks confirm that our RVV-optimized kernel runs about 10 times faster than the default, unoptimized executor_runner.

Figure 1. Measurement results: baseline (unoptimized) kernel vs. optimized RVV kernel

Dataset Accuracy Evaluation (ImageNet Validation)

To ensure that the minor numerical deviations in the 7th and 8th decimal places did not degrade model performance, we decided to run an accuracy evaluation using the full ImageNet validation dataset.

We downloaded the ImageNet validation subset from Kaggle: ImageNet Mini 1000 Dataset on Kaggle

Resizing the Storage Partition

After converting the validation images into raw formats, we attempted to copy the dataset onto the board's /sharefs folder. However, we quickly hit storage limits. We wrote an automation script to loop executor_runner through all raw images, but it regularly crashed due to a lack of disk space.

To resolve this issue, we extended the storage partition hosting /sharefs by following the instructions from the Kendryte K230 FAQ Guide:

[root@canaan /sharefs ]#df -h
Filesystem                Size      Used      Available  Use% Mounted on
/dev/root              118.5M     86.4M     28.2M  75% /
devtmpfs                13.0M         0     13.0M   0% /dev
tmpfs                   51.7M         0     51.7M   0% /dev/shm
tmpfs                   51.7M     52.0K     51.6M   0% /tmp
tmpfs                   51.7M     44.0K     51.7M   0% /run
/dev/mmcblk1p4         255.9M    198.1M     57.8M  77% /sharefs

[root@canaan ~ ]#parted -l /dev/mmcblk1
Warning: Not all of the space available to /dev/mmcblk1 appears to be used...
Fix/Ignore? fix
Model: SD SL32G (sd/mmc)
Disk /dev/mmcblk1: 31.9GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End     Size    File system  Name        Flags
1      10.5MB  31.5MB  21.0MB               rtt
2      31.5MB  83.9MB  52.4MB               linux
3      134MB   268MB   134MB   ext4         rootfs
4      268MB   537MB   268MB   fat16        fat32appfs  msftdata

[root@canaan ~ ]#umount /sharefs/
[root@canaan ~ ]#parted -a minimal /dev/mmcblk1 resizepart 4 8.5GB
[root@canaan ~ ]#parted -l /dev/mmcblk1
[root@canaan ~ ]#mkfs.ext2 /dev/mmcblk1p4
[root@canaan ~ ]#parted -l /dev/mmcblk1
Model: SD SL32G (sd/mmc)
Disk /dev/mmcblk1: 31.9GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End      Size     File system  Name        Flags
1      10.5MB  31.5MB   21.0MB               rtt
2      31.5MB  83.9MB   52.4MB               linux
3      134MB   268MB    134MB    ext4         rootfs
4      268MB   8500MB   8232MB   ext2         fat32appfs  msftdata

[root@canaan ~ ]#mount /dev/mmcblk1p4 /sharefs/

[root@canaan ~ ]#df -h
Filesystem                Size      Used      Available  Use% Mounted on
/dev/root              118.5M     86.4M     28.2M  75% /
devtmpfs                13.0M         0     13.0M   0% /dev
tmpfs                   51.7M         0     51.7M   0% /dev/shm
tmpfs                   51.7M     52.0K     51.6M   0% /tmp
tmpfs                   51.7M     48.0K     51.6M   0% /run
/dev/mmcblk1p4          7.5G     17.3M      7.1G   0% /sharefs

Final Accuracy Results

Because the dataset's subdirectories were named using standard ImageNet synset IDs (e.g., n01440764), we used a reference file named LOC_synset_mapping.txt to map these IDs to human-readable names. For instance, the entry n01440764 tench, Tinca tinca maps the folder ID to class index 1, representing a "tench" fish.

We calculated the Top-1 and Top-5 accuracy metrics, for both runners, across the entire validation dataset for both runners. The evaluations confirmed that the minor float precision variations from vector calculations caused no change in classification accuracy.

Both setups gave identical evaluation results:

Unoptimized Kernel Metrics:

==============================
OVERALL TOP-1 ACCURACY
==============================
2790/3923 (71.12%)

==============================
OVERALL TOP-5 ACCURACY
==============================
3535/3923 (90.11%)

Optimized RVV Kernel Metrics:

==============================
OVERALL TOP-1 ACCURACY
==============================
2790/3923 (71.12%)

==============================
OVERALL TOP-5 ACCURACY
==============================
3535/3923 (90.11%)

Our results align with the official PyTorch MobileNetV2 Model Documentation, which reports:

  • Top-1 Accuracy:878% (~71.9%)
  • Top-5 Accuracy:286% (~90.3%)

Dušan Stojković

You may also like