news 2026/9/15 21:56:48

MNN 模型转换工具 MNNConvert 实战指南:多格式转换、图优化、量化压缩与正确性校验

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MNN 模型转换工具 MNNConvert 实战指南:多格式转换、图优化、量化压缩与正确性校验

MNN 模型转换工具 MNNConvert 实战指南:多格式转换、图优化、量化压缩与正确性校验

【免费下载链接】MNNMNN: A blazing-fast, lightweight inference engine battle-tested by Alibaba, powering high-performance on-device LLMs and Edge AI.项目地址: https://gitcode.com/GitHub_Trending/mn/MNN

MNN 的模型转换工具 MNNConvert 负责将 TensorFlow、TFLite、Caffe、ONNX、TorchScript 等格式的模型转换为 MNN 推理格式(.mnn),是模型部署到 MNN 引擎前的必经环节。本文围绕 官方转换文档 完整展开:从各参数含义与取舍、各格式的标准转换命令、--testdir正确性校验体系,到 MNN 与 Json 的双向转换、模型 Dump 与 MNN2QNNModel 等进阶工具,帮助读者完整掌握模型上端侧转换、压缩与问题排查的全流程。

工具入口与源码结构

MNNConvert 的可执行文件由 tools/converter/CMakeLists.txt 在MNN_BUILD_CONVERTER选项开启时构建,入口实现位于 tools/converter/source/MNNConverter.cpp:

int main(int argc, char *argv[]) { modelConfig modelPath; // parser command line arg auto res = MNN::Cli::initializeMNNConvertArgs(modelPath, argc, argv); if (!res) { return modelPath.cliExitCode; } // Convert return MNN::Cli::convertModel(modelPath) ? 0 : 1; }

从源码结构看,所有命令行参数由 tools/converter/include/cli.hpp 中定义的Cli类解析到modelConfig结构,随后统一走Cli::convertModel完成转换。转换器按框架拆分为独立模块,均位于tools/converter/source/下:tensorflow/onnx/tflite/caffe/torch/optimizer/(图优化)、compression/(量化压缩)等;对应的转换器类声明在 tools/converter/include 目录中(如onnxConverter.hpptorchConverter.hpp)。

需要注意两点构建前提:

  • TorchScript 转换需要显式开启-DMNN_BUILD_TORCH=ON(见 tools/converter/CMakeLists.txt 中MNN_BUILD_TORCH选项),默认关闭;
  • TF/TFLite/ONNX/Caffe 转换依赖 Protobuf,CMake 会优先使用仓库内置的3rd_party/protobuf,否则find_package(Protobuf REQUIRED)

从源码编译的完整流程可参考 docs/compile/other.md;若通过pip install MNN安装,可直接使用内置的mnnconvert,见 docs/tools/python.md。

MNNConvert 参数说明

以下为官方文档给出的完整参数列表(./MNNConvert -h输出),是日常转换最核心的参考:

Usage: MNNConvert [OPTION...] -h, --help Convert Other Model Format To MNN Model -v, --version 显示当前转换器版本 -f, --framework arg 需要进行转换的模型类型, ex: [TF,CAFFE,ONNX,TFLITE,MNN,TORCH, JSON] --modelFile arg 需要进行转换的模型文件名, ex: *.pb,*caffemodel --batch arg 如果模型时输入的batch是动态的,可以指定转换后的batch数 --keepInputFormat 是否保持原始模型的输入格式,默认为:是; --optimizeLevel arg 图优化级别,默认为1: - 0: 不执行图优化,仅针对原始模型是MNN的情况; - 1: 保证优化后针对任何输入正确; - 2: 保证优化后对于常见输入正确,部分输入可能出错; --optimizePrefer arg 图优化选项,默认为0: - 0:正常优化 - 1:优化后模型尽可能小; - 2:优化后模型尽可能快; --prototxt arg caffe模型结构描述文件, ex: *.prototxt --MNNModel arg 转换之后保存的MNN模型文件名, ex: *.mnn --fp16 将conv/matmul/LSTM的float32参数保存为float16, 模型将减小一半,精度基本无损,运行速度和float32模型一致 --bizCode arg MNN模型Flag, ex: MNN --debug 使用debug模型显示更多转换信息 --forTraining 保存训练相关算子,如BN/Dropout,default: false --weightQuantBits arg arg=2~8,此功能仅对conv/matmul/LSTM的float32权值进行量化, 仅优化模型大小,加载模型后会解码为float32,量化位宽可选2~8, 不开启动态量化的情况下,运行速度和float32模型一致。8bit时精度基本无损,模型大小减小4倍 default: 0,即不进行权值量化 --weightQuantAsymmetric 与weightQuantBits结合使用,决定是否用非对称量化,默认为`true` --compressionParamsFile arg 使用MNN模型压缩工具箱生成的模型压缩信息文件或根据用户提供的量化参数来生成对应的量化模型,量化参数文件可参考tools/converter/user_provide_quant_params.json 。如果文件不存在,且开启了weightQuantBits等量化功能,会在相应路径生成模型压缩信息文件(json格式),可后续编辑 --saveStaticModel 固定输入形状,保存静态模型, default: false --targetVersion arg 兼容旧的推理引擎版本,例如:1.2f --customOpLibs arg 用户自定义Op库,用于TorchScript模型中自定义算子的实现,如:libmy_add.so --info 当-f MNN时,打印模型基本信息(输入名、输入形状、输出名、模型版本等) --authCode arg 认证信息,指定模型的认证信息,可用于鉴权等逻辑 --inputConfigFile arg 保存静态模型所需要的配置文件, ex: ~/config.txt。文件格式为: input_names = input0,input1 input_dims = 1x3x224x224,1x3x64x64 --testdir arg 测试转换 MNN 之后,MNN推理结果是否与原始模型一致。 arg 为测试数据的文件夹,生成方式参考 "正确性校验" 一节 --thredhold arg 当启用 --testdir 后,设置正确性校验的误差允可范围 若不设置,默认是 0.01 --JsonFile arg 当-f MNN并指定JsonFile时,可以将MNN模型转换为Json文件 --alignDenormalizedValue arg 可选值:{0, 1}, 默认为1, 当`float(|x| < 1.18e-38)`会被视为0 --detectSparseSpeedUp 检测权重是否使用稀疏化加速/压缩,有可能减少模型大小,但增大模型转换时间 --saveExternalData 将权重,常量等数据存储在额外文件中,默认为0,也就是`false` --useGeluApproximation 在进行Gelu算子合并时,使用Gelu的近似算法,默认为1 ,也就是`true` --useOriginRNNImpl LSTM和GRU算子是否使用原始算子实现,默认关闭。若开启,性能可能提升,但无法进行LSTM/GRU的量化

重点参数详解

--optimizeLevel--optimizePrefer:图优化的两个正交维度。--optimizeLevel控制优化的"激进程度":默认的 1 级保证任意输入下优化结果都正确;2 级只保证常见输入正确,换取更强的优化空间;0 级则完全跳过图优化,仅对源模型本身就是 MNN 的场景有意义。--optimizePrefer在优化时选择偏向:0 为常规优化,1 偏向模型体积最小,2 偏向推理速度最快。图优化的具体实现位于 tools/converter/source/optimizer 模块。

--weightQuantBits:纯体积优化的权值量化。官方文档特别强调:该选项仅对 conv/matmul/LSTM 的 float32 权值做 2~8 bit 量化,只优化模型大小——加载模型时权值会解码回 float32,因此运行速度与 float32 模型一致;8bit 时精度基本无损,模型大小约减小 4 倍。可与--weightQuantAsymmetric(默认true)组合控制是否采用非对称量化。

--compressionParamsFile:与压缩工具箱联动的量化参数文件。该参数可传入 MNN 模型压缩工具箱生成的 json 压缩信息文件,或用户自建的量化参数文件(格式参考仓库中的 tools/converter/user_provide_quant_params.json)。若文件不存在且开启了--weightQuantBits等量化功能,转换器会在该路径自动生成一份可编辑的量化参数 json,供二次调优后重新转换——这是"先探测、后定制"的两阶段量化流程。

--fp16:与权值量化互不冲突的存储压缩。将 conv/matmul/LSTM 的 float32 参数以 float16 存储,模型体积减半、精度基本无损、运行速度与 float32 模型一致,属于"零性能代价"的压缩手段,可与--weightQuantBits分别评估后组合使用。

--keepInputFormat:与后端数据布局的取舍。官方文档说明2指出:如果使用 Interpreter-Session C++ 接口开发,由于 NC4HW4 布局便于与 ImageProcess 结合,转换时可加--keepInputFormat=0让模型使用 MNN 自动内存布局。

--saveStaticModel+--inputConfigFile:静态形状模型。需要固定输入形状时(例如部分 NPU 后端只支持静态形状),用--saveStaticModel保存静态模型,并通过--inputConfigFile提供的配置文件指定输入名与形状,格式为:

input_names = input0,input1 input_dims = 1x3x224x224,1x3x64x64

--testdir/--thredhold:转换时的就地正确性校验。指定--testdir指向一组测试数据文件夹后,转换器会对比 MNN 推理结果与原始模型结果;--thredhold设置误差允许范围,默认为 0.01。测试数据的生成方式见下文"正确性校验"一节。

TorchScript 专属参数。--customOpLibs可传入用户自定义 Op 的 so 库(如libmy_add.so),用于 TorchScript 模型中自定义算子的转换;--useOriginRNNImpl决定 LSTM/GRU 是否走原始算子实现——开启后性能可能提升,但代价是无法对 LSTM/GRU 做量化。

各格式模型转换到 MNN

TensorFlow to MNN

./MNNConvert -f TF --modelFile XXX.pb --MNNModel XXX.mnn --bizCode biz

注意:*.pb必须是 frozen model,不能使用 saved_model

TensorFlow Lite to MNN

./MNNConvert -f TFLITE --modelFile XXX.tflite --MNNModel XXX.mnn --bizCode biz

Caffe to MNN

./MNNConvert -f CAFFE --modelFile XXX.caffemodel --prototxt XXX.prototxt --MNNModel XXX.mnn --bizCode biz

Caffe 模型需要同时提供权重文件与--prototxt网络结构描述文件。

ONNX to MNN

./MNNConvert -f ONNX --modelFile XXX.onnx --MNNModel XXX.mnn --bizCode biz

TorchScript to MNN

./MNNConvert -f TORCH --modelFile XXX.pt --MNNModel XXX.mnn --bizCode biz

注意:TorchScript 模型要求使用torch.jit导出的模型,不要直接使用 PyTorch 的权重文件作为转换输入。官方给出的导出方式:

import torch # ... # model is exported model model.eval() # trace model_trace = torch.jit.trace(model, torch.rand(1, 3, 1200, 1200)) model_trace.save('model_trace.pt') # script model_script = torch.jit.script(model) model_script.save('model_script.pt')

MNN to Json / Json to MNN

想了解 MNN 模型的具体结构与输入输出信息时,可将模型转为 Json 查阅:

./MNNConvert -f MNN --modelFile XXX.mnn --JsonFile XXX.json

反过来,可以对 Json 进行编辑修改后再转回 MNN 模型,达到修改微调模型的目的:

./MNNConvert -f JSON --modelFile XXX.json --MNNModel XXX.mnn

正确性校验

为便于开发者排查问题,MNN 针对 PB / Tflite / Onnx / TorchScript 提供了一组正确性校验脚本(位于 tools/script 目录),检查 MNN 推理结果是否与原始模型一致:

  • testMNNFromTf.py:适用 pb
  • testMNNFromTflite.py:适用 tflite
  • testMNNFromOnnx.py:适用 onnx
  • testMNNFromTorch.py:适用 pt (torchscript)

注意事项

  • 如果模型是动态输入形状,MNN 在脚本中默认不固定部分为 1,有可能在 Tensorflow / OnnxRuntime / Torch 验证阶段报错。此时需要修改脚本中对应的输入部分,比如testMNNFromOnnx.py中的run_onnx(self)函数,把输入替换为有效的输入形状和内容。
  • 对于由 TorchScript 转换的模型,一般都需要自行修改testMNNFromTorch.py中的输入信息来测试。
  • 如果模型输出层是 Identity 产生的,会因为 MNN 图优化的缘故丢失,此时需要校验上一层的输出,即在脚本后接输出名来测试,如python3 ../tools/script/testMNNFromTf.py XXX.pb $NAME$

前置环境

  • 测试 pb / tflite:安装tensorflowpip install tensorflow
  • 测试 onnx:安装onnxruntimepip install onnxruntime
  • 测试 torchscript:安装torchpip install torch
  • 【可选】MNN 模型转换工具编译完成(编译后产生MNNConvert可执行文件)

使用方式

在 MNN 的build目录(包含MNNConvert)下运行:

python3 ../tools/script/testMNNFromTf.py SRC.pb # Onnx 为 testMNNFromOnnx.py,Tflite 类似

若最终结果为TEST_SUCCESS,则表示 MNN 的模型转换与运行结果正确。若路径下没有编译好的MNNConvert可执行文件,脚本会回退使用 pymnn 进行校验。

以 ONNX 为例的完整实例(来自官方文档的真实运行日志):

cd build cmake -DMNN_BUILD_CONVERTER=ON .. && make -j4 python ../tools/script/testMNNFromOnnx.py mobilenetv2-7.onnx # 模型转换后推理并与ONNXRuntime结果对比 Dir exist onnx/test.onnx tensor(float) ['output'] inputs: input onnx/ outputs: onnx/output.txt (1, 1000) onnx/ Test onnx Start to Convert Other Model Format To MNN Model... [21:09:40] /Users/wangzhaode/copy/AliNNPrivate/tools/converter/source/onnx/onnxConverter.cpp:40: ONNX Model ir version: 6 Start to Optimize the MNN Net... 108 op name is empty or dup, set to Const108 109 op name is empty or dup, set to BinaryOp109 110 op name is empty or dup, set to Unsqueeze110 112 op name is empty or dup, set to Unsqueeze112 97 op name is empty or dup, set to Unsqueeze97 98 op name is empty or dup, set to Const98 inputTensors : [ input, ] outputTensors: [ output, ] Converted Success! input output: output output: (1, 1000, ) TEST_SUCCESS

默认只支持限定数值范围的输入随机生成,如需修改请自行修改脚本。

出错定位与解决

  • 出现Test Error或者 MNN 的 crash 可直接反馈(提 issue 或到钉钉群反馈)。
  • 如需自查,脚本提供 debug 功能,可方便定位出错的 layer / op,示例:python3 testMNNFromOnnx.py SRC.onnx DEBUG

以 ONNX 为例的错误排查过程:假设转换器中某算子实现被人为改错(官方文档以将 Binary_ADD 实现修改为错误实现作实验),运行脚本后输出TESTERROR,表明模型可以转换但推理结果有错误:

python ../tools/script/testMNNFromOnnx.py mobilenetv2-7.onnx ... output: (1, 1000, ) TESTERROR output value error : absMaxV:5.814904 - DiffMax 32.684010 Error for output output Save mnn result to .error director

对于推理出错的情况,可以逐层校验定位错误层(官方文档示例采用类似二分的方式逐层验证):

# test layer output 365: ERROR python ../tools/script/testMNNFromOnnx.py mobilenetv2-7.onnx 365 ... 365: (1, 32, 28, 28, ) TESTERROR 365 value error : absMaxV:3.305553 - DiffMax 5.069034 Error for output 365 Save mnn result to .error director # binary search test layers ... # test layer output 339: ERROR, 339's inputs is [489, 498] python ../tools/script/testMNNFromOnnx.py mobilenetv2-7.onnx 339 ... TESTERROR 339 value error : absMaxV:3.704849 - DiffMax 5.504766 # test layer output 489: SUCCESS python ../tools/script/testMNNFromOnnx.py mobilenetv2-7.onnx 489 ... TEST_SUCCESS # test layer output 498: SUCCESS python ../tools/script/testMNNFromOnnx.py mobilenetv2-7.onnx 498 ... TEST_SUCCESS # so bug is layer 339

对 ONNX 模型还可以使用自动定位功能:在模型参数后加DEBUG,脚本会执行基于支配树的二分查找,直至找到错误层:

python ../tools/script/testMNNFromOnnx.py mobilenetv2-7.onnx DEBUG ... Test Node : Conv_14 True ### First Error Node is : Add_15

算子支持列表查询

各框架下转换器支持的算子列表可以直接通过--OP打印,用于判断某算子是否被覆盖:

./MNNConvert -f CAFFE --OP ./MNNConvert -f TF --OP ./MNNConvert -f ONNX --OP ./MNNConvert -f TORCH --OP

模型打印:Dump 成可读的类 Json 格式

将 MNN 模型文件 dump 成可读的类 json 格式文件,方便对比原始模型参数,也可以对模型进行修改。可以使用MNNConvertMNNDump2Json将模型转换成 Json 文件;修改后还可以使用MNNConvertMNNRevert2Buffer将 Json 文件转回 MNN 模型(MNNDump2Json/MNNRevert2Buffer两个可执行文件分别由 tools/converter/source/MNNDump2Json.cpp 和 tools/converter/source/MNNRevert2Buffer.cpp 构建):

./MNNDump2Json mobilenet_v1.mnn mobilenet_v1.json # do some change in mobilenet_v1.json ./MNNRevert2Buffer mobilenet_v1.json mobilenet_v1_new.mnn

Dump 结果展示了完整的网络结构,例如:

cat mobilenet_v1.json { "oplists": [ { "type": "Input", "name": "data", "outputIndexes": [ 0 ] , "main_type": "Input", "main": { "dims": [ 1, 3, 224, 224 ] , "dtype": "DT_FLOAT", "dformat": "NC4HW4" } , "defaultDimentionFormat": "NHWC" } , { "type": "Convolution", "name": "conv1", "inputIndexes": [ 0 ] , "outputIndexes": [ 1 ] , "main_type": "Convolution2D", "main": { "common": { "dilateX": 1, "dilateY": 1, "strideX": 2, "strideY": 2, "kernelX": 3, "kernelY": 3, "padX": 1, "padY": 1, "group": 1, "outputCount": 32, "relu": true, "padMode": "CAFFE", "relu6": false, "inputCount": 0 } , weight: [ -0.0, -0.0, 0.0, -0.0, ... ] , bias: [ -0.000004, 0.694553, 0.416608, ... ] } , "defaultDimentionFormat": "NHWC" } , ... ] , "tensorName": [ "data", "conv1", "conv2_1/dw", "conv2_1/sep", ... ] , "sourceType": "CAFFE", "bizCode": "AliNNTest", "tensorNumber": 0, "preferForwardType": "CPU" }

从示例可以看到,Json 中记录了每个 op 的类型与连接关系(inputIndexes/outputIndexes)、卷积的完整参数(stride、padding、group、outputCount、relu 等)以及 weight/bias 数值,末尾还带有sourceTypebizCode等全局元信息——这正是用"Json 中转"方式微调模型时可靠操作的依据。

Python 版 mnnconvert

MNN 提供预编译的 MNNConvert Python 工具mnnconvertpip install MNN后即可在 Python 中完成格式转换,具体用法参考 docs/tools/python.md。

MNN2QNNModel:生成 QNN 后端的替代模型与离线产物

功能

该工具针对特定的高通硬件架构,为原始的 MNN 模型生成 MNN-QNN 后端需要的替代模型以及 QNN 离线产物。目前支持静态形状的模型以及有限输入形状组合的模型。

运行环境要求

该工具必须在 x86_64 架构的 Linux 系统上运行(部分 QNN SDK 中的离线工具必须在此环境中运行)。

编译

添加额外的 CMAKE 变量并编译:

-DMNN_QNN=ON -DMNN_QNN_CONVERT_MODE=ON -DMNN_WITH_PLUGIN=OFF -DMNN_BUILD_TOOLS=ON -DMNN_SUPPORT_TRANSFORMER_FUSE=ON

用法

./MNN2QNNModel <qnnSDKPath> <socId> <hexagonArch> <srcMNNPath> <outputDir> [totalShapeNum] [inputShape1] [inputShape2] ...

参数配置说明:

参数说明是否必须
<qnnSDKPath>QNN SDK 的根目录路径。
<socId>目标 SoC 的 ID。常用 ID 参考:8Gen2 ->43, 8Gen3 ->57, 8 Elite ->69。其他型号请参考高通官方文档。
<hexagonArch>Hexagon 架构版本。常用架构参考:8Gen2 ->73, 8Gen3 ->75, 8 Elite ->79。其他型号请参考高通官方文档。
<srcMNNPath>待转换的原始 MNN 模型文件路径(.mnn文件)。
<outputDir>用于存放生成产物的目录。工具会在此目录下生成一个新的.mnn文件(替代模型)和一个.bin文件(QNN 离线产物)。
[totalShapeNum]需要支持的动态输入形状的总数量。
[inputShapeN]具体的输入形状配置。根据totalShapeNum的数量,提供相应个数的形状描述。形状信息可以是以下两种格式之一:1.形状字符串:例如1x3x512x512。对于多输入模型,用下划线_分隔,例如1x3x512x512_1x256。2.MNN 文件路径:提供一个包含所需输入信息的.mnn文件路径。
示例

假设 QNN SDK 路径为/path/to/qnn/sdk,目标设备为 8Gen3(socId=57, hexagonArch=75),原始模型为model.mnn,输出目录为/path/to/output

  • 使用默认输入形状进行转换:
./MNN2QNNModel /path/to/qnn/sdk 57 75 model.mnn /path/to/output
  • 为单输入模型指定单种输入形状:
./MNN2QNNModel /path/to/qnn/sdk 57 75 model.mnn /path/to/output 1 1x3x256x256
  • 为单输入模型指定多种输入形状:
./MNN2QNNModel /path/to/qnn/sdk 57 75 model.mnn /path/to/output 2 1x3x256x256 1x3x512x512
  • 为多输入模型指定多种输入形状:
./MNN2QNNModel /path/to/qnn/sdk 57 75 model.mnn /path/to/output 2 1x3x256x256_1x100 1x3x512x512_1x200
产物

工具执行成功后,会在指定的<outputDir>目录下生成两个文件,文件名由原始模型名、SoC ID 和 Hexagon 架构版本共同决定,格式为<原始模型名>_<socId>_<hexagonArch>.<suffix>

  • 替代模型.mnn文件,文件名格式为<原始模型名>_<socId>_<hexagonArch>.mnn
  • QNN 离线产物.bin文件,包含优化后的模型和权重,文件名格式为<原始模型名>_<socId>_<hexagonArch>.bin

例如上述示例的产物位于/path/to/output/目录下:

/path/to/output/ ├── model_57_75.mnn # 替代模型 └── model_57_75.bin # QNN离线产物

关于如何使用这些产物,可进一步参考 QNN 离线构图模式的使用说明。

compilefornpu:LLM 相关模型的分段 NPU 转换

对于较复杂的模型,可以通过compilefornpu及对应的npu_convert.py分段转换为 NPU。该工具目前仅在 LLM 相关模型的转换中使用,属于 LLM 端侧部署链路中的专用转换工具。

小结

MNN 的转换工具体系以 MNNConvert 为核心,覆盖"格式转换 → 图优化 → 体积压缩 → 正确性验证 → 结构级修改"的完整链路:

  • 格式转换支持 TF(frozen pb)、TFLite、Caffe、ONNX、TorchScript(需-DMNN_BUILD_TORCH=ON)与 MNN/Json 互转;
  • 体积优化可组合使用--fp16(体积减半)、--weightQuantBits(加载时解码,速度不变)与--compressionParamsFile(两阶段定制量化参数,见 tools/converter/user_provide_quant_params.json);
  • 正确性校验用 tools/script 下的testMNNFrom*.py脚本对比原始框架推理结果,支持指定层校验与DEBUG支配树自动定位;
  • 结构级调试用MNNDump2Json/MNNRevert2Buffer完成 MNN ↔ Json 双向转换;
  • 面向高通 NPU 的MNN2QNNModel则负责生成 QNN 替代模型与离线产物。

理解这些工具参数背后的取舍——优化级别与正确性保证、量化对体积和速度的不同影响、静态/动态形状约束——是高效完成模型端侧部署与问题定位的关键。

【免费下载链接】MNNMNN: A blazing-fast, lightweight inference engine battle-tested by Alibaba, powering high-performance on-device LLMs and Edge AI.项目地址: https://gitcode.com/GitHub_Trending/mn/MNN

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

Ozlo睡眠监测平台:医疗级精度与消费级体验的融合

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

作者头像 李华
网站建设 2026/9/14 20:04:45

基于Vue的uni-app小程序开发:农家书屋项目全解析

简介&#xff1a;基于Vue框架的农家书屋小程序设计源码&#xff0c;是一套面向小程序开发者和前端学习者的完整工程&#xff0c;适合用Vue技术栈构建乡村数字化阅读服务场景。资源共含941个文件&#xff0c;压缩包约25.94MB&#xff0c;主要涵盖248个JavaScript脚本、229个Vue组…

作者头像 李华
网站建设 2026/9/14 20:04:10

DeepSeek 跑 128K 长文档总结:Key 用 TaoToken

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

作者头像 李华
网站建设 2026/9/14 20:03:52

QGIS中CRS选择与坐标转换实践指南

1. QGIS中的CRS基础概念与选择逻辑在地理信息系统&#xff08;GIS&#xff09;工作中&#xff0c;坐标参考系统&#xff08;Coordinate Reference System&#xff0c;简称CRS&#xff09;的选择直接影响空间数据的定位精度和后续分析结果。QGIS作为开源GIS软件的代表&#xff0…

作者头像 李华