“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。
md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。
要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。
在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。
要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。
工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。
写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。
适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。
你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。
全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。
这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。
不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。
产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。
如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。
Stop Guessing, Start Profiling: Mastering Edge AI Performance and Power on Android
好的,没问题。这篇原文讲的是 Android 端侧 AI 的性能和功耗分析,干货满满。我把它重新组织成一篇深度技术笔记,就像我自己踩过坑、调过参之后,写下来分享给团队同事的那种。我们开始吧。
先别急着调模型,先看看你的 App 在“吃”多少电
想象一下这个场景:你在工作站上花了几周时间优化模型,剪枝、量化、调参,一套组合拳下来,推理速度起飞了。你信心满满地把它部署到用户的 Android 手机上。结果呢?三分钟不到,App 开始掉帧,手机发烫,原本“快如闪电”的 AI 功能,现在输出一个 token 都费劲。
你是不是也遇到过?这其实就是撞上了 “功耗墙” (Power Wall)。
在端侧 AI 的世界里,性能从来不只是“模型跑多快”的问题,它更关乎“跑一次要耗多少电”和“会带来多少热量”。如果你开发端侧 AI 应用,却从没用过 Android Studio 的 Power Profiler(功耗分析器),那说句不好听的,你根本不是在开发,你是在“猜”。
为什么手机电池会“尿崩”?聊聊端侧 AI 的物理课
要真正搞懂功耗分析,我们不能只盯着“电池百分比”这个表象。当我们在手机上部署像 Gemini Nano 这样的端侧模型时(通过 AICore),本质上是在 CPU、GPU 和 NPU 之间编排一场高能耗的“舞蹈”。
热量、降频和“搬砖”的代价
在硬件层面,运行一个神经网络涉及数十亿次的乘加运算(MAC)。很多人以为瓶颈是算力(TFLOPS),但真相是,对于端侧 AI,最大的瓶颈往往是数据搬运的能量消耗。
每一次数据从 RAM 搬到处理器的寄存器里,都要耗电。当 NPU 使用率飙到 100% 时,它会产生高度集中的热量。如果手机的散热跟不上,Android 系统就会触发 热降频 (Thermal Throttling)。
这是系统层面的干预,内核会通过 动态电压频率调整 (DVFS) 来降低 SoC 的时钟频率。作为开发者,你看到的现象就是:App 跑了没几分钟,推理速度突然断崖式下跌,毫无道理。
Power Profiler 的价值就在于此:它能让你亲眼看到这种关联——能量消耗曲线飙升,紧接着性能曲线就跳水了。
端侧 AI 的“不可能三角”
每一个端侧 AI 开发者都在玩一个“三角博弈”,需要在三个相互制约的目标中寻找平衡:
- 精度 (Accuracy):更高的精度(比如 FP32)意味着更好的结果,但功耗也更大。
- 延迟 (Latency):用更快的硬件(GPU/NPU)能减少等待时间,但会产生更高的热量峰值。
- 能耗 (Energy):量化到 INT8 能降低功耗,但可能导致精度下降。
Profiling 的目标,就是找到那个 帕累托最优 (Pareto Optimal) 的点:一个让你的模型“精度够用”、“速度达标”、“散热良好”的配置,让用户用得爽。
新时代的架构:AICore 和 Gemini Nano
Google 通过 AICore 彻底改变了游戏规则。以前,开发者是把 .tflite 文件直接打进 APK 里的。这简直是效率灾难:每个 App 都有一份模型副本,导致磁盘空间严重浪费和内存重复分配。
AICore 是一个系统级的服务,它把端侧 AI 模型当作共享资源来管理。你可以把它想象成“AI 版的 Google Play Services”。
这个架构带来了三个巨大的好处:
- 模型可更新:Google 可以通过系统更新来更新 Gemini Nano 的权重,而你完全不需要动你的 APK。
- 内存效率:如果三个不同的 App 都在用 Gemini Nano,模型权重只需要加载到内存一次,然后通过只读内存映射 (read-only memory map) 共享给所有 App。
- 硬件抽象:就像 CameraX 抽象了不同的相机硬件一样,AICore 抽象了底层的 NPU。不管手机用的是高通 Hexagon DSP、Google Tensor TPU 还是 ARM Ethos NPU,你的 API 调用都是一致的。
搞清楚你的模型到底跑在哪个“引擎”上
要做有效的 Profiling,你必须知道你的模型正在哪个“引擎”上运行。如果在 Power Profiler 里看到推理期间 CPU 占用率很高,那说明有“泄漏”——你的模型很可能因为某个算子不被 NPU 支持,而回退到 CPU 上运行了。
- NPU (神经网络处理单元):黄金标准。它利用大规模并行计算和本地化内存 (SRAM) 来最大限度地减少数据搬运,是最高效的选择。
- GPU (图形处理器):很擅长 AI 需要的浮点运算,但功耗比 NPU 高得多。可以作为备选,但要小心热降频。
- DSP (数字信号处理器):“永远在线”的哨兵。它处理低复杂度的任务(比如唤醒词检测),功耗可以忽略不计。
优化精要:量化和剪枝
如果你的 Power Profiler 显示“内存”轨道的功耗比“计算”轨道还高,那你就该关注 量化 (Quantization) 了。
把一个 32 位浮点数 (FP32) 从 RAM 搬到 NPU 是非常耗能的。通过把你的模型量化成 INT8 (8位整数),你不仅仅是将模型体积缩小了 4 倍,更重要的是,你降低了算术逻辑单元 (ALU) 里晶体管的“翻转率”。这让整个运算过程的能效提升了几个数量级。
剪枝 (Pruning) 则更进一步,它直接移除掉那些“死掉的”神经元。在 Power Profiler 里,成功的剪枝表现为功耗尖峰的“持续时间”变短了,因为 NPU 更快地完成了计算,然后迅速回到低功耗休眠状态 (C-state)。
动手实战:构建一个可 Profiling 的 AI 工作负载
光说不练假把式。要看到真实的效果,你需要一个可控的工作负载。下面我们来实现一个实时图像分类的 pipeline,用 TensorFlow Lite,并且专门设计了开关,让你可以在 Power Profiler 里切换 CPU 和 GPU,观察能量消耗的变化。
技术栈
这个例子会用 Hilt 做依赖注入,用 Kotlin 协程做异步编排,还有 TFLite GPU 委托。
1. AI 推理仓库 (Inference Repository)
这个类管理 TFLite 的生命周期。注意看这里使用了 Direct ByteBuffer 来避免昂贵的 JNI 内存拷贝——这是减少 CPU 开销的关键细节。
@Singleton
class InferenceRepository @Inject constructor(
private val context: Context
) {
private var interpreter: Interpreter? = null
private var gpuDelegate: GpuDelegate? = null
private val modelPath = "mobilenet_v2.tflite"
fun initializeModel(useGpu: Boolean) {
closeInterpreter()
val options = Interpreter.Options().apply {
if (useGpu) {
// 关键点:把张量运算从 CPU 卸载到 GPU
// 在 Power Profiler 里观察,CPU 轨道能耗下降,GPU 轨道能耗上升
gpuDelegate = GpuDelegate()
addDelegate(gpuDelegate)
} else {
setNumThreads(4) // 使用 CPU 多线程
}
}
interpreter = Interpreter(loadModelFile(), options)
}
fun runInference(inputBuffer: ByteBuffer): FloatArray {
val outputBuffer = Array(1) { FloatArray(1001) }
interpreter?.run(inputBuffer, outputBuffer)
return outputBuffer[0]
}
private fun loadModelFile(): ByteBuffer {
val fileInputStream = FileInputStream(context.assets.openFd(modelPath))
val fileChannel = fileInputStream.channel
return fileChannel.map(
FileChannel.MapMode.READ_ONLY,
fileChannel.position(),
fileChannel.size()
)
}
fun closeInterpreter() {
interpreter?.close()
gpuDelegate?.close()
interpreter = null
gpuDelegate = null
}
}
2. AI ViewModel
在端侧 AI 开发里,主线程是神圣不可侵犯的。我们用 Dispatchers.Default 来确保繁重的张量操作不会阻塞 UI 线程,导致界面卡顿。
@HiltViewModel
class AIViewModel @Inject constructor(
private val repository: InferenceRepository
) : ViewModel() {
private val _inferenceResult = MutableStateFlow("Ready")
val inferenceResult: StateFlow<String> = _inferenceResult.asStateFlow()
private val _isGpuEnabled = MutableStateFlow(false)
val isGpuEnabled: StateFlow<Boolean> = _isGpuEnabled.asStateFlow()
fun toggleHardware() {
val useGpu = !_isGpuEnabled.value
_isGpuEnabled.value = useGpu
viewModelScope.launch(Dispatchers.Default) {
repository.initializeModel(useGpu)
// 这里可以触发一次预热推理,让 Power Profiler 的数据更干净
// runInferenceOnSampleImage()
}
}
// 模拟连续推理,制造可观测的功耗负载
fun startBurstInference() {
viewModelScope.launch(Dispatchers.Default) {
repeat(100) {
// 这里应该从 Camera 或 Bitmap 获取真正的图像数据
val dummyInput = ByteBuffer.allocateDirect(1 * 224 * 224 * 3 * 4) // 假设输入是 224x224 RGB 图像
val result = repository.runInference(dummyInput)
_inferenceResult.value = "Inference $it: Top class index = ${result.indices.maxByOrNull { result[it] }}"
// 稍微延迟一下,让功耗曲线看得更清楚
delay(50)
}
}
}
override fun onCleared() {
super.onCleared()
repository.closeInterpreter()
}
}
怎么用 Power Profiler 看效果?
- 打开 Android Studio,运行你的 App。
- 点击底部的
Profiler标签。 - 在
Sessions面板,选择你的设备和进程。 - 点击
Energy时间线。 - 在 App 里,先点击
Toggle Hardware按钮切换到 CPU 模式,然后点击Start Burst Inference。 - 观察 CPU 轨道的能耗。你会看到一个明显的峰值,并且手机温度会逐渐上升。
- 等推理结束,再次点击
Toggle Hardware切换到 GPU 模式,再次点击Start Burst Inference。 - 观察能耗变化。你会看到 CPU 轨道几乎没动静了,但 GPU 轨道(如果有的话)或者总的能耗曲线,可能会在初期有一个更高的峰值(因为 GPU 启动有开销),但单个推理的能耗和完成速度可能更快。如果手机支持 NPU,并且你能通过 AICore 调用,那效果会更明显。
关键观察点:
- CPU 模式:能耗曲线平缓但持续时间长,容易触发热降频,导致后期推理速度越来越慢。
- GPU 模式:能耗曲线峰值更高,但单个推理耗时更短,总的能量消耗(曲线下的面积)可能更小。如果散热差,依然可能降频。
- 理想状态 (NPU):能耗曲线峰值低,耗时极短,热量积累最慢。
总结
别再做“性能猜谜”了。Power Profiler 不是可选项,而是端侧 AI 开发的必备工具。它能帮你把抽象的“手机发烫”和“App 变慢”具体化为可见的能耗曲线和频率变化。下次再遇到性能问题,先别急着调模型结构,打开 Profiler,看看你的电都耗在哪了,是计算瓶颈还是数据搬运瓶颈?是 CPU 回退还是热降频?找到根因,你的优化才能事半功倍。