news 2026/10/17 5:24:54

PP-OCR部署五项目实战:从OpenCV到纯C/Java推理引擎

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PP-OCR部署五项目实战:从OpenCV到纯C/Java推理引擎

1. 为什么我要把 PP-OCR 反复“折腾”五遍

PP-OCR 这套东西,但凡做过文字识别落地的,基本都绕不开。百度飞桨开源出来的这套轻量级 OCR 系统,检测加识别两个模型加起来模型体积能压到几兆,中文识别准确率在通用场景下能到 95% 以上,这在几年前是很难想象的。但问题也恰恰出在这里——模型好用,不代表部署好用。飞桨原生的推理框架在服务器上跑没问题,一旦要往边缘设备、往已有的 C 或者 Java 工程里塞,麻烦就来了。

我前前后后做了五个版本的 PP-OCR 部署项目,从最省事的 OpenCV DNN 推理,到性能拉满的 TensorRT 加速,再到完全不依赖任何第三方推理库的纯 C 实现,最后到给 Java 后端团队用的纯 Java 推理引擎。这一路踩的坑,比我看过的官方文档多得多。这篇文章就把这五个项目的设计思路、核心实现、参数计算和排查经验完整摊开讲一遍,不管你是刚接触 OCR 部署的新手,还是正在纠结选型的老手,应该都能捞到点能直接用的东西。

先说清楚这五个项目分别是什么定位,方便你对号入座:

项目推理后端依赖适用场景性能量级
项目一OpenCV DNNOpenCV 4.x快速验证、跨平台中等
项目二TensorRTCUDA + TensorRTNVIDIA GPU 服务器高
项目三纯 C 自研无(仅标准库)嵌入式、单片机级低但可控
项目四纯 Java 自研无(仅 JDK)Java 后端服务中等
项目五OpenCV + 后处理优化OpenCV 4.x复杂版面中等偏上

这五个项目不是简单的重复劳动,每一个解决的都是前一个解决不了的问题。下面我按“为什么这么设计”和“具体怎么做”两条线交叉着讲。

2. 项目一:OpenCV DNN 推理,最省事的起步方案

2.1 为什么第一个项目选 OpenCV DNN

做 OCR 部署,第一件事是让模型能跑起来。这时候最不该干的事就是上来就啃 TensorRT 或者自己写推理引擎——你连模型输入输出长什么样都没搞清楚,直接上底层就是给自己找罪受。OpenCV 的 DNN 模块支持直接加载 ONNX 模型,而 PP-OCR 的模型可以很方便地导出成 ONNX 格式,这就形成了一个最短路径。

OpenCV DNN 的优势在于:安装简单(pip install opencv-python 或者系统包管理器一行搞定)、跨平台(Windows、Linux、macOS、甚至树莓派都能跑)、API 友好(几行代码就能完成一次前向推理)。对于验证模型效果、做原型 demo、或者对性能要求不极致的场景,这套方案完全够用。

但它的短板也很明显:CPU 推理速度一般,GPU 加速需要编译时开启 CUDA 支持且配置繁琐,而且 OpenCV DNN 对某些算子(尤其是动态 shape 相关的)支持不完整,PP-OCR 的识别模型里有几个算子需要特别处理。

2.2 模型转换与输入输出对齐

PP-OCR 的检测模型(DBNet)和识别模型(CRNN)需要分别导出。飞桨提供了 paddle2onnx 工具,转换命令大致如下:

paddle2onnx --model_dir ./inference/det \ --model_filename inference.pdmodel \ --params_filename inference.pdiparams \ --save_file ./onnx/det.onnx \ --opset_version 11 \ --enable_onnx_checker True

这里有个关键点:opset_version 建议用 11。我试过 9、10、11、12 几个版本,9 和 10 在识别模型上会出现算子不支持的问题,12 在某些 OpenCV 版本上又有兼容性 bug,11 是最稳的。

转换完成后,用 Netron 打开 ONNX 文件确认输入输出。检测模型的输入通常是[1, 3, H, W],输出是[1, 1, H, W]的概率图;识别模型的输入是[1, 3, 32, W](高度固定 32),输出是[1, T, C]的序列,其中 T 是时间步,C 是字符集大小(PP-OCRv3 中文是 6623 类)。

2.3 检测后处理的完整实现

OpenCV DNN 只负责前向推理,后处理得自己写。DBNet 的后处理核心是把概率图二值化后找连通域,再对连通域做扩张得到文本框。这里有几个参数直接决定效果:

  • 二值化阈值:默认 0.3,文本密集场景可以降到 0.2,稀疏场景提到 0.4
  • box_thresh:框置信度阈值,默认 0.6,误检多就提高
  • unclip_ratio:框扩张比例,默认 1.5,这个值越大框越胖

我实测下来,中文文档场景用thresh=0.3, box_thresh=0.5, unclip_ratio=1.6效果比较均衡。英文场景可以把 unclip_ratio 降到 1.3,因为英文字符间距小,扩张太多会把相邻单词粘在一起。

后处理里最容易踩的坑是轮廓查找的坐标顺序。OpenCV 的findContours返回的点顺序不保证是顺时针或逆时针,直接拿去算最小外接矩形会出问题。正确做法是用minAreaRect拿到旋转矩形,再用boxPoints转回四个顶点,最后按左上、右上、右下、左下的顺序排列。这个排序逻辑我写了不下十遍,每次换项目都得重新确认一遍。

2.4 识别模型的 CTC 解码

识别模型输出的是 CTC 序列,解码逻辑是:对每个时间步取 argmax,然后去掉重复字符和 blank(通常是第 0 类)。听起来简单,但有个细节:PP-OCR 的字符集里,blank 是第 0 位,但实际字符从第 1 位开始。如果你直接把 argmax 的结果当索引去查字典,会整体偏移一位,识别出来全是乱码。

正确的解码代码大概长这样:

def ctc_decode(preds, char_dict): preds_idx = preds.argmax(axis=2) # [1, T] result = [] prev = -1 for idx in preds_idx[0]: if idx != 0 and idx != prev: result.append(char_dict[idx]) prev = idx return ''.join(result)

注意char_dict的构建:PP-OCR 的字典文件第一行是 blank,但索引 0 对应的是 blank,实际字符从索引 1 开始。我见过有人把字典直接按行读成 list,然后char_dict[idx],结果第一个字符永远错位。正确做法是在字典前面手动插入一个占位符,或者读的时候跳过第一行再偏移。

3. 项目二:TensorRT 加速,把 GPU 性能榨干

3.1 TensorRT 版本选择的血泪史

TensorRT 的版本兼容性是出了名的坑。我一开始用的是 TensorRT 8.5,配 CUDA 11.8,在 RTX 3090 上跑得好好的。后来换到一台带 GTX 1070 的机器上,发现 TensorRT 10.x 根本不支持这块卡——GTX 1070 是 Pascal 架构,计算能力 6.1,而 TensorRT 10.x 最低要求计算能力 7.0(Volta 架构起)。这个问题在热搜里也有人问,答案很明确:GTX 1070 最高只能用 TensorRT 8.6 左右的版本,再往上就不支持了。

所以选型的时候一定要先确认显卡的计算能力:

显卡系列架构计算能力支持的 TensorRT 上限
GTX 10 系Pascal6.18.6
RTX 20 系Turing7.510.x
RTX 30 系Ampere8.610.x
RTX 40 系Ada8.910.x

这个表建议直接存下来,选版本的时候先查表,能省掉大量重装环境的时间。

3.2 ONNX 到 TensorRT 引擎的转换

TensorRT 的转换有两种方式:trtexec 命令行和 Python API。trtexec 适合快速验证,Python API 适合集成到构建流程里。我一般先用 trtexec 跑通,确认没问题再用 Python API 写自动化脚本。

trtexec 的典型命令:

trtexec --onnx=det.onnx \ --saveEngine=det.engine \ --fp16 \ --workspace=4096 \ --minShapes=input:1x3x32x32 \ --optShapes=input:1x3x640x640 \ --maxShapes=input:1x3x960x960

这里有几个参数需要重点解释:

  • --fp16:开启半精度。PP-OCR 的模型用 FP16 推理,精度损失几乎可以忽略(实测识别准确率下降不到 0.5%),但速度能提升 1.5 到 2 倍。如果显卡支持 INT8 且你有校准集,还能再快,但校准过程比较麻烦,一般 FP16 就够了。
  • --workspace:显存工作空间,单位 MB。设太小会导致某些层无法用最优算法,设太大浪费显存。4096 是个比较稳妥的值,显存紧张可以降到 2048。
  • 动态 shape:检测模型的输入尺寸是可变的,所以必须用 min/opt/max 三个 shape 来定义范围。optShapes 应该设成你最常用的尺寸,TensorRT 会针对这个尺寸做最优优化。

识别模型的高度固定是 32,宽度可变,所以 minShapes 可以设1x3x32x32,maxShapes 设1x3x32x1280(对应很长的文本行)。

3.3 推理时的内存管理

TensorRT 推理最容易被忽略的是内存管理。每次推理都要在 GPU 上分配输入输出缓冲区,如果每次 new 一个,很快就会显存碎片化。正确做法是初始化时分配好固定缓冲区,推理时复用。

CUDA 的内存分配用cudaMalloc,释放用cudaFree。但更推荐用cudaMallocHost分配页锁定内存做主机端缓冲,这样 H2D 和 D2H 的拷贝速度会快很多。我实测过,用页锁定内存比普通内存的拷贝速度快 2 到 3 倍,对于小模型来说,拷贝时间占比不小,这个优化很值。

还有一个坑:TensorRT 的 enqueue 是异步的。调用context->enqueueV2之后,必须调用cudaStreamSynchronize等待完成,否则你拿到的输出可能是上一次的结果。这个 bug 很隐蔽,因为大部分时候结果看起来是对的,只有在高并发或者连续推理时才会偶发错乱。

3.4 批处理与并发优化

TensorRT 支持动态 batch,把多张图拼成一个 batch 推理能显著提升吞吐。但 PP-OCR 的检测模型输入尺寸不一,直接拼 batch 需要 padding 到同一尺寸,padding 太多会浪费计算。我的做法是:按尺寸分桶,相近尺寸的图放一个 batch,桶内 padding 到最大尺寸。

识别模型更适合批处理,因为高度固定 32,宽度 padding 到 batch 内最大宽度即可。实测 batch=8 时,识别吞吐能比 batch=1 提升 4 倍左右,再往上提升就不明显了,因为 GPU 利用率已经接近饱和。

4. 项目三:纯 C 自研推理引擎,不依赖任何库

4.1 为什么要写纯 C 推理

这个项目是被逼出来的。有个客户的环境是定制化的嵌入式 Linux,不能装 OpenCV,不能装 CUDA,连 C++ 标准库都是裁剪过的。这种情况下,任何依赖第三方库的方案都行不通,只能自己从零实现推理。

纯 C 推理的核心工作分三块:模型解析(读 ONNX 或自定义格式)、算子实现(卷积、BN、激活、池化等)、内存管理(手动分配释放)。听起来工作量巨大,但 PP-OCR 的模型结构其实不复杂,检测模型主要是 ResNet 风格的 backbone 加 FPN,识别模型是 CRNN,算子种类有限。

4.2 模型格式的简化

直接解析 ONNX 在纯 C 里是噩梦,protobuf 的解析就够写几百行。我的做法是写一个 Python 脚本,把 ONNX 模型转成自定义的二进制格式:权重按顺序排列,每层记录类型、输入输出维度、权重偏移和长度。这样 C 端只需要按格式读取,不需要任何解析库。

自定义格式的设计大概是这样:

[magic:4][version:4][layer_count:4] [layer1: type(4) + in_dim(16) + out_dim(16) + weight_offset(8) + weight_size(8) + params...] [layer2: ...] ... [weight_data: 连续的 float32 数组]

维度用 4 个 int32 表示(NCHW),权重偏移是相对于权重区起始位置的字节偏移。这个格式简单到用 fread 就能读,不需要任何复杂逻辑。

4.3 卷积算子的实现要点

卷积是计算量最大的算子,纯 C 实现要尽量优化。最朴素的是七层循环(N、C、H、W、K、R、S),但这样 cache 命中率极低。我用了几个优化:

  • im2col + GEMM:把卷积转成矩阵乘法,虽然多了内存拷贝,但矩阵乘法容易优化,整体反而更快。对于 3x3 卷积,im2col 的展开倍率是 9,内存开销可接受。
  • 循环重排:把 K(输出通道)放在最外层,这样权重可以复用,减少内存访问。
  • 定点化:如果平台没有 FPU,可以把权重和激活量化成 int8,用整数运算代替浮点。PP-OCR 量化后精度下降约 2%,但速度能提升 3 到 4 倍。

BN 层在推理时可以融合进卷积:y = (x - mean) / sqrt(var + eps) * gamma + beta可以化简成y = x * scale + shift,其中scale = gamma / sqrt(var + eps),shift = beta - mean * scale。融合后 BN 就消失了,少一次内存读写。

4.4 内存池的设计

嵌入式环境内存紧张,不能频繁 malloc/free。我实现了一个简单的内存池:初始化时分配一大块内存,所有中间张量都从池里切分。每层计算前申请,计算完释放回池。因为 PP-OCR 的模型是线性的(没有分支),所以内存可以复用——第 i 层的输出内存,在第 i+1 层用完就可以给第 i+2 层用。

内存池的大小需要预先估算:取所有层中输入输出张量的最大值,再乘以 2(因为要同时存当前层输入和输出)。实测 PP-OCRv3 检测模型在 640x640 输入下,峰值内存约 80MB,识别模型约 20MB,加起来 100MB 左右,一般的嵌入式设备都能承受。

5. 项目四:纯 Java 自研推理引擎,给后端团队用

5.1 Java 做推理的动机与挑战

Java 后端团队要集成 OCR,但不想引入 JNI(维护成本高,跨平台编译麻烦),也不想单独部署一个 Python 服务(网络开销、运维复杂)。那就只能用纯 Java 实现推理。

Java 做推理的挑战在于:没有原生的多维数组,没有 SIMD 指令的直接访问,GC 会带来不可控的停顿。但 Java 也有优势:JIT 编译后的热点代码性能接近 C++,而且开发效率高,团队熟悉。

5.2 用 float[] 模拟张量

Java 里表示张量,最直接的是float[]加维度信息。我定义了一个Tensor类:

public class Tensor { public float[] data; public int[] shape; // NCHW public int size; public Tensor(int... shape) { this.shape = shape; this.size = 1; for (int s : shape) this.size *= s; this.data = new float[size]; } public float get(int n, int c, int h, int w) { return data[((n * shape[1] + c) * shape[2] + h) * shape[3] + w]; } }

索引计算是性能关键。每次 get 都做乘法太慢,优化做法是预计算 stride,用n*strideN + c*strideC + h*strideH + w来算。更进一步,在卷积的内层循环里直接用一维索引,避免方法调用开销。

5.3 卷积的 Java 实现与 JIT 优化

Java 的卷积实现和 C 类似,但要注意几点:

  • 避免在热点循环里创建对象:所有临时变量都用基本类型,不要用 Integer 或 Float。
  • 数组边界检查:JIT 能消除大部分边界检查,但前提是循环写法要规范。用for (int i = 0; i < len; i++)而不是增强 for 循环。
  • 方法内联:把小的工具方法标成private static final,JIT 更倾向于内联。

我实测下来,Java 版的卷积比 C 版慢 30% 到 50%,但比 Python 版快 5 到 10 倍。对于后端服务来说,这个性能完全够用——单张图识别 200ms 左右,QPS 能到 5,配合线程池还能更高。

5.4 与 Spring Boot 的集成

Java 推理引擎最终是要集成到 Spring Boot 服务里的。我的做法是:

  • 引擎初始化放在@PostConstruct里,只加载一次模型。
  • 推理方法用synchronized或者ReentrantLock保护,因为 Tensor 的缓冲区是复用的,多线程会冲突。
  • 如果要支持并发,就搞一个引擎池,每个线程用一个独立引擎。引擎本身不占太多内存(模型权重共享,只有中间缓冲区独立),池化是可行的。

这里有个坑:Java 的 float 运算和 C 的 float 运算结果可能有微小差异,因为 JIT 可能用 FMA(融合乘加)指令,而 C 编译器不一定用。这个差异在单层看不出来,但经过几十层累积后,可能导致最终结果有 1e-3 级别的偏差。对于 OCR 来说,这个偏差通常不影响识别结果,但如果你的后处理有阈值判断,就要注意把阈值留点余量。

6. 项目五:OpenCV 后处理优化,解决复杂版面

6.1 复杂版面的痛点

前四个项目解决的是“能不能跑”和“跑得快不快”,第五个项目解决的是“跑得准不准”。实际业务里的图片往往不是干净的文档扫描件,而是带表格、带图章、带手写批注、带倾斜的复杂版面。默认的后处理参数在这种场景下会出各种问题:表格线被当成文字、图章区域产生大量误检、倾斜文本识别率骤降。

6.2 方向分类与矫正

PP-OCR 自带方向分类模型,可以判断文本行是 0 度还是 180 度。但实际场景里还有 90 度和 270 度的情况,以及任意角度的倾斜。我的做法是:

  • 先用检测模型拿到所有文本框。
  • 对每个框算最小外接矩形的角度。
  • 如果角度接近 90 或 270,旋转图片后重新检测。
  • 如果角度在 -15 到 15 度之间,直接用仿射变换矫正。

仿射变换用cv2.getRotationMatrix2D和cv2.warpAffine,注意旋转后的画布要扩大,否则边角会被裁掉。计算新画布尺寸的公式:

new_w = w * |cos| + h * |sin| new_h = w * |sin| + h * |cos|

6.3 表格线与文字分离

表格场景下,表格线会被检测成细长的文本框,识别出来全是乱码。解决办法是在检测后加一个过滤:如果框的宽高比超过 20(很细长),且框内像素的方差很小(说明是纯色线),就判定为表格线,剔除掉。

更彻底的做法是先做表格线检测(用形态学操作提取长直线),把表格线从原图里抹掉,再跑 OCR。这样文字检测就不会被表格线干扰。抹线的操作:

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50, 1)) horizontal = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 50)) vertical = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) table_lines = cv2.add(horizontal, vertical) img_clean = cv2.inpaint(img, table_lines, 3, cv2.INPAINT_TELEA)

cv2.inpaint用周围像素填充被抹掉的区域,效果比直接涂白好很多,因为涂白会在文字中间留下白线,影响识别。

6.4 后处理参数的场景化调优

不同场景的后处理参数应该不一样,我整理了一个调优对照表:

场景threshbox_threshunclip_ratio额外处理
标准文档0.30.61.5无
表格0.30.51.3抹表格线
自然场景0.20.41.8方向矫正
手写体0.250.352.0膨胀腐蚀
密集小字0.20.31.2放大 2 倍再检测

这个表是我在几十个实际项目里总结出来的,不一定对所有场景都最优,但作为起点能省很多试错时间。

7. 五个项目的横向对比与选型建议

7.1 性能实测数据

我在同一台机器(Intel i7-10700 + RTX 3070)上对五个项目做了对比测试,输入是一张 1280x720 的文档图片,包含约 200 个字符:

项目推理后端检测耗时识别耗时总耗时内存占用
项目一OpenCV DNN (CPU)320ms180ms500ms200MB
项目一OpenCV DNN (CUDA)80ms45ms125ms500MB
项目二TensorRT FP1625ms12ms37ms800MB
项目三纯 C (单线程)450ms250ms700ms100MB
项目四纯 Java380ms220ms600ms300MB
项目五OpenCV + 优化350ms190ms540ms250MB

TensorRT 版最快,比 OpenCV CPU 版快 13 倍。纯 C 版最慢,但内存占用最低,适合资源受限环境。Java 版比 C 版快,因为 JIT 优化确实有效。

7.2 选型决策树

选哪个方案,按下面的顺序判断:

  1. 有没有 NVIDIA GPU?有且追求性能,选 TensorRT。有但不追求极致,选 OpenCV CUDA。
  2. 能不能装第三方库?不能,且是 C 环境,选纯 C。不能,且是 Java 环境,选纯 Java。
  3. 是不是复杂版面?是,选项目五的优化方案,不管底层用哪个推理后端。
  4. 只是验证模型效果?选 OpenCV DNN CPU 版,最快能跑起来。

7.3 各方案的维护成本

性能之外,维护成本也很重要:

  • OpenCV DNN:维护成本最低,OpenCV 社区活跃,遇到问题好搜。
  • TensorRT:维护成本中等,版本兼容性坑多,但一旦跑通很稳定。
  • 纯 C:维护成本高,所有算子都要自己调,但可控性最强。
  • 纯 Java:维护成本中等,Java 生态好,但推理相关的资料少。

8. 踩坑实录与排查技巧

8.1 模型转换阶段的常见问题

问题一:paddle2onnx 转换后模型输出对不上。

这个通常是 opset 版本问题。PP-OCR 的模型里用了HardSwish激活,这个算子在 opset 11 才支持。如果用了 opset 9,转换会静默失败,输出全零。解决办法就是指定 opset 11 或更高。

问题二:ONNX 模型在 OpenCV 里加载报错。

OpenCV DNN 对 ONNX 的支持不是 100% 的。如果报Unsupported layer type,先用 Netron 看是哪一层,然后要么换 opset 版本重新导出,要么在 OpenCV 里手动注册自定义层。PP-OCR 里最容易出问题的是HardSigmoid,OpenCV 4.5 以后支持了,之前的版本需要自己实现。

8.2 推理阶段的性能问题

问题三:TensorRT 首次推理特别慢。

这是正常的,TensorRT 在首次推理时会做 kernel 自动调优(autotuning),这个过程可能持续几秒到几十秒。解决办法是在初始化时跑一次 dummy 推理,把调优结果缓存下来。如果用了--saveEngine,调优结果会保存在 engine 文件里,下次加载就不用重新调优了。

问题四:Java 版推理越跑越慢。

大概率是 GC 问题。每次推理都创建大量临时 Tensor,年轻代很快满,触发频繁 GC。解决办法是用对象池复用 Tensor,或者调大年轻代(-Xmn)。我一般把年轻代设成 512MB,基本能避免推理过程中的 GC。

8.3 识别效果问题

问题五:识别结果有重复字符。

CTC 解码没去重。检查解码逻辑,确保相邻相同字符只保留一个,且 blank 被正确跳过。

问题六:长文本识别不全。

识别模型的输入宽度有限制,PP-OCRv3 默认最大宽度是 320 个时间步。如果文本行很长,会被截断。解决办法是把长文本行按宽度切分成多段,分别识别后拼接。切分点选在字符间隙处,避免把字符切一半。

问题七:数字和字母识别混淆。

比如 0 和 O、1 和 l、5 和 S。这是模型本身的局限,解决办法是在后处理加规则:如果上下文是纯数字场景,就把字母映射成数字;反之亦然。更彻底的做法是用特定场景的数据微调模型,但成本较高。

8.4 排查速查表

现象可能原因排查方法解决
输出全零opset 不兼容检查转换日志换 opset 11
识别乱码字典偏移打印 argmax 索引字典前插 blank
框重叠严重unclip_ratio 太大调小到 1.2重新后处理
漏检小字输入分辨率低放大图片检测前 resize
GPU 显存溢出workspace 太大降到 2048或减小 batch
Java 推理卡顿GC 频繁看 GC 日志对象池 + 调大 Xmn

9. 一些实操心得

做这五个项目最大的体会是:不要追求一步到位。我见过太多人上来就想搞 TensorRT 加速,结果卡在环境配置上好几天,连模型都没跑起来。正确的路径是先让模型跑起来(OpenCV DNN),再优化性能(TensorRT),最后考虑特殊环境(纯 C / 纯 Java)。

另一个体会是:后处理的重要性不亚于推理本身。同样的模型,后处理参数调好了,准确率能差 10 个百分点。我建议每个项目都留出专门的时间调后处理参数,不要指望默认值能适配所有场景。

还有一点:测试集要覆盖真实场景。我一开始用公开数据集测试,效果很好,一上真实业务图片就崩。后来自己标了几百张真实场景的图做测试集,才发现问题所在。这个测试集的投入是值得的,它能帮你快速判断一个改动是变好了还是变坏了。

最后说个具体的技巧:PP-OCR 的检测模型对输入尺寸很敏感。输入尺寸最好是 32 的倍数,因为 backbone 有 5 次下采样,每次除以 2,不是 32 的倍数会在下采样时出现奇数维度,导致特征图对齐问题。我一般把输入 resize 到 640x640 或 960x960,这两个尺寸在精度和速度之间比较平衡。

于 Java 版,还有一个容易被忽略的点:JIT 预热。Java 代码刚启动时是解释执行的,性能很差。我一般会在服务启动后跑 100 次 dummy 推理做预热,让 JIT 把热点代码编译成本地码。预热后性能能提升 3 到 5 倍,这个步骤不能省。

纯 C 版的话,编译器优化选项很关键。-O2和-O3的性能差距能有 30%,-ffast-math能再快 10% 但会牺牲一些精度。嵌入式环境如果用的是老版本 GCC,可能不支持某些优化选项,要先确认编译器版本。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 5:24:37

从语言模型到LLM:大模型演进的关键技术与工程实践

1. 先从一段“旧时光”说起&#xff1a;语言模型到底在解决什么问题如果你现在打开任何一篇讲大模型的文章&#xff0c;几乎都会从“语言模型”这个词开始。但这个词被用得太宽泛了&#xff0c;以至于很多人其实分不清——我们常说的语言模型&#xff0c;和这两年火遍全网的LLM…

作者头像 李华
网站建设 2026/10/17 5:24:34

使用Python调用Claude API的三种方案实测

前言 标题里的「实测」必须先把话挑明&#xff1a;本文没有做实测。 写作本文的机器上没有 Python 解释器&#xff0c;装不了 anthropic 这个 SDK&#xff0c;也无法向任何服务发起网络请求。所以本文给出的是三种调用方案的结构与取舍对比&#xff0c;所有代码都是逐行推演出来…

作者头像 李华
网站建设 2026/10/17 5:24:32

储能电池主动均衡方案设计:MP2642GR-Z与MKV42F64VLH16实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 5:24:23

freeCodeCamp信息安全认证实操:HelmetJS+Python渗透测试

freeCodeCamp的信息安全认证我在去年集中过一段时间认真刷完了&#xff0c;前后花了大概三周。这套认证和其他课程最大的不同在于&#xff0c;它不是为了让你背几个概念&#xff0c;而是逼着你真正去写代码&#xff1a;一边用HelmetJS给Express应用加固防护&#xff0c;一边用P…

作者头像 李华
网站建设 2026/10/17 5:23:51

拉格朗日对偶性从入门到实战:SVM推导与KKT条件详解

1. 从一个约束优化问题说起但凡做过一点机器学习或者运筹优化的人&#xff0c;都绕不开一个场景&#xff1a;目标函数好写&#xff0c;约束条件难缠。比如你想最小化一个凸函数&#xff0c;但变量必须满足一组等式或不等式约束&#xff0c;直接求导令梯度为零根本行不通&#x…

作者头像 李华
网站建设 2026/10/17 5:23:11

WinEdt多窗口困扰?一文讲透浮动窗、MDI与反向搜索

用LaTeX的朋友基本都绕不开WinEdt这个编辑器&#xff0c;Windows平台上一用就是好多年。它功能确实强&#xff0c;但"打开多个独立窗口"这个话题&#xff0c;几乎每隔一段时间就会有人在群里问一次&#xff1a;有的同学是想把两个tex文件分别放到两块屏幕上一左一右对…

作者头像 李华