江门模板建站源码大连成久建设工程有限公司

张小明 2026/1/8 9:43:36
江门模板建站源码,大连成久建设工程有限公司,全国建筑企业资质查询系统官网,如何在网站页面添加代码1.前言 本文旨在提供 征程 6H/P 计算平台的部署指南#xff0c;将会从硬件、软件两部分进行介绍#xff0c;本文整理了我们推荐的使用流程#xff0c;和大家可能会用到的一些工具特性#xff0c;以便于您更好地理解工具链。某个工具具体详 l 细的使用说明#xff0c;还请…1.前言本文旨在提供 征程 6H/P 计算平台的部署指南将会从硬件、软件两部分进行介绍本文整理了我们推荐的使用流程和大家可能会用到的一些工具特性以便于您更好地理解工具链。某个工具具体详 l 细的使用说明还请参考用户手册。2.征程 6H/P 硬件配置2.1 BPU®Nash2.2 硬件规格BPUDSP​ ​算力TAE​ ​​浮点输出征程 6E80TN征程 6M128TN征程 ​6P560TY征程 6H420TY征程 6B-Base18TYBPU 内部器件TAEBPU 内部的张量加速引擎主要用于 Conv、MatMul、Linear 等 Gemm 类算子加速VAEBPU 内部的 SIMD 向量加速引擎主要用于完成 vector 计算VPUBPU 内部的 SIMT 向量加速单元主要用于完成 vector 计算SPUBPU 内部的 RISC-V 标量加速单元主要用于实现 TopK 等算子APMBPU 内部另一块 RISC-V 标量加速单元主要用于 BPU 任务调度等功能L1M一级缓存BPU 核内共享L2M二级缓存BPU 核间共享2.3 与其他征程 6 计算平台的主要区别​TAE​征程 6B/H/P 支持 fp16 和 fp32 输出而征程 6E/M 不支持浮点输出。这使得在征程 6B/H/P 计算平台上配置模型尾部 conv 高精度输出输出精度是 fp32而在征程 6E/M 计算平台上配置模型尾部 conv 高精度输出输出精度是 int32后接一个反量化节点转 fp32。若在征程 6E/M 计算平台上是删除尾部反量化部署的话迁移征程 6B/H/P 时软件代码需要注意适配。征程 6E/M 尾部高精度 conv 输出 int32:征程 6B/H/P 尾部高精度 conv 输出 float32:​VAE​征程 6H/P 支持 fp16​L2M​征程 6H/P 支持 L2 缓存多 BPU 核共用征程 6E/M/B 单核无 L2 缓存。通过命令cat /sys/kernel/debug/ion/heaps/custom查看 L2M 大小。​跨距对齐要求不同​征程 6H/P 要求模型 nv12 输入 stride 要求满足 64 对齐征程 6E/M/B 则是要求 32 对齐。同时模型其他输入输出节点的对齐要求也有可能不同。针对 nv12 输入金字塔配置文件需要注意修改 stride 参数如果征程 6E/M/B 内存够的话建议可直接按 64 对齐来申请跨平台迁移时就无需更改配置。针对其他输入输出 tensor建议编译时打开编译参数input_no_paddingTrue, output_no_paddingTrue。或是按推荐方式结合 stride 和 valid_shape 来解析有效数据也可避免跨平台迁移适配。​最小内存单元不同​征程 6H/P tensor 最小申请内存是 256 字节征程 6E/M 64 字节征程 6B 128 字节。3.新功能特性若已有其他平台使用经验可只关注本章节内容了解征程 6HP 与其他征程 6 计算平台的功能点差异即可。相较于征程 6E/M/B征程 6H/P 最主要区别是多核TAE/VAE/VPU 器件能力的增强以及增加了 L2M本章节将介绍这几点差异对于在征程 6H/P 平台上开发算法方案的影响。3.1 多核部署3.1.1 多核模型编译征程 6H/P 硬件支持单帧多核的部署方式但是当前多核模型特指单次模型推理同时使用了两个及两个以上 BPU 核心的模型功能还在开发中目前支持了 resnet50 双核模型的 demo性能数据见下表单核模型实测延时ms双核模型实测延时msResnet509.11875.7458由于 BPU 是独占式硬件若运行双核模型则代表该模型运行期间有两个 bpu 会被同时占用无法运行其他任务加上多核模型相较于单核能拿到的性能收益与模型结构紧密相关很难确保理想的双核利用率。因此出于更高的跨平台迁移效率和硬件资源利用率等因素考虑建议按下一节建议拆分模型部署。3.1.2 pipeline 设计征程 6H/P 分别提供了 3 个和 4 个 BPU 计算核心给了应用调度更灵活的设计空间通过多核充分并行可有效减少系统端到端延时。以下方案仅为示例并非是标准推荐算法架构示意图部署 pipeline 设计由于工具难以感知模型上下游关系任务重要程度不同设计帧率等信息且多核模型利用率提升难度很大因此建议用户手动拆分不同功能的模型以提高多核计算资源的利用率。拆分逻辑有如下建议​多任务帧率不同​智驾系统中各个子任务设计帧率可能是不同的建议拆分部署。​无上下游依赖​两个没有上下游输入输出数据依赖的模型建议拆分部署编译在一起也是顺次执行的。拆分后通过放在不同核心上部署可以缩短整体系统的端到端延时。​跨团队开发提前做资源分配​算法功能开发团队约定算力分配后可独立开发优化独立上线测试若编译在一起则每次发版都会有相互依赖。3.2 合理使用 L2M由于征程 6H/P 算法方案相对会比征程 6E/M/B 的复杂包括接入的摄像头数目模型前后处理和模型体量变大都会导致整个系统对带宽的需求要高很多。由于带宽争抢不可避免当多核同时运行时会发现模型延时相较于独占硬件测试时会变长。为了缓解带宽争抢导致模型延时变长的现象征程 6H/P 提供了 L2M可使部分与 DDR 交换的数据被缓存在 L2M 内。建议在大部分模型都产出 hbm 后甚至 pipeline 大致确定之后使用如下方式离线评估所有模型的带宽资源使用情况测试不同 l2 配置的带宽收益。3.2.1 L2M 使用说明需要更新到 OE3.5.0 及以上。当前仅支持以 BPU 核为粒度配置 L2 大小暂不支持运行时实时对单模型做配置。启用 L2M 涉及到模型编译以及运行时正确制定环境变量两项工作。开发板实际可用 l2m 大小请使用命令 cat /sys/kernel/debug/ion/heaps/custom进行查看。3.2.1.1 模型编译编译时通过指定参数控制模型可用的 l2 大小from hbdk4.compiler import load, convert, compile compile(quantized_bc, ···, max_l2m_sizel2m*1024*1024) # max_l2m_size单位为bytesl2m0及为默认配置不启用L2l2m6即为6Ml2m12为12M。 # 详细说明请阅读用户手册 - 进阶内容 - HBDK Tool API Reference - compile3.2.1.2 模型推理无需改动推理代码只需通过环境变量控制每个核可申请的 l2 大小暂不支持运行时动态申请建议部署在相同 BPU 核上的模型编译时指定相同的 L2M 大小否则需要按最大需求来配置。export HB_DNN_USER_DEFINED_L2M_SIZES6:6:6:6 # 每个核分配6M export HB_DNN_USER_DEFINED_L2M_SIZES12:0:0:12 # 核0和核3各分到12M不正确的 L2M 使用可能导致如下问题未给对应核分配足够的 L2M 或是没有分配 L2M推理将会失败打印如下提示日志信息“model [{model name}] node [{node name}] L2 memory not enough, required l2 memspace info: [{model L2M}], user-assigned l2 memspace size: [{HB_DNN_USER_DEFINED_L2M_SIZES}], user-assigned cores: [{core_id}]比如模型编译时指定了 12M L2M运行时只通过环境变量给该核分配了 6M或是运行时忘记配置环境变量。发现没有带宽收益或者推理结果错乱老版本 ucp 也能推理带 l2 的模型只不过会出现推理结果不正确并且没有带宽收益的问题请从日志里确认 ucp 的版本已经升级到 OE3.5.0 及以上集成的版本。3.2.2 统计并优化系统带宽由于目前 hbm_perf 暂不支持 L2Mperf 看不出 l2m 的收益预计 2025 年底的版本可支持因此具体收益需要通过实测获取。按照经验实测与预估偏差非常小10% 以内通过预估方式如果发现四个核带宽占用差不多可以直接每个核平分 l2如果核 0 和核 3 的带宽占用最为显著可以直接将 l2 平均分给两个核的模型。3.2.2.1 按实车 pipeline 设计预估平均带宽的方式首先使用 hbm_perf 评测模型从 html 或 json 中获取带宽信息结合设计帧率评估模型上线后预计需要的带宽资源平均带宽GB/s DDR bytes per second( for n FPS) / n * 设计帧率/2^30以下面这个模型为例实车设计帧率为 10FPS则实车时该模型需要的平均带宽为68138917200/10.39*10/2^30 61.08GB/s3.2.2.2 按实车 pipeline 实测平均带宽的方式修改 hrt_model_exec 工具支持按设计帧率 perf 模型如何修改工具以及带宽数据如何分析请参考社区文章https://developer.horizon.auto/blog/13054找一个空闲的开发板用 hrt_model_exec 工具按设计帧率 perf 模型hrt_model_exec perf --model_file model.hbm --perf_fps 20 --frame_count 2000 --core_id 1使用 hrut_ddr 获取 bpu 占用的平均带宽hrut_ddr -t bpu -p 1000000 # 统计周期拉长到1s看平均值即可默认-p是1000即1ms采样一次瞬时带宽受采样影响不太准确3.3 量化配置由于征程 6H/P 的硬件增强了浮点能力为了降低量化难度提高模型迭代效率建议初始量化配置使用全局 float16Conv 类算子回退 int8。排除 int-float 的量化反量化开销征程 6H/P 上大多数 vector 计算int16 精度和 float16 精度计算速度相当因此建议 vector 计算精度直接使用 float16若基础配置精度不达标后续依据敏感度对 conv 类计算加 int16 即可。经实践证明除了部分模型有中间计算数值范围太大超过了 fp16 表示范围需要切换 int16 之外fp16 能有效降低 qat 量化难度。更多关于征程 6H/P 精度调优流程的说明请参考后文 4*.3.3 精度调优流程* 章节。OE3.5.0 为了支持征程 6H/P 用户更便捷高效的配置浮点精度horizon-plugin-pytorch 对 qconfig 配置做了优化若您使用的是旧版本的配置方式建议参考文档【地平线 征程 6 工具链入门教程】QAT 新版 qconfig 量化模板使用教程https://developer.horizon.auto/blog/13112升级使用新的模版。3.4 部署差异3.4.1 模型输出精度可能不同由于征程 6B/H/P 的 TAE 硬件支持 fp16 和 fp32 输出而征程 6E/M 不支持因此若模型以 GEMM 类算子结尾的话征程 6E/M 配置高精度输出是 int32征程 6B/H/P 配置高精度输出是 fp32。因此征程 6E/M 模型直接编译到征程 6B/H/P模型输出类型有可能会发生改变软件代码需要注意适配。3.4.2 跨距对齐要求不同征程 6H/P 要求 nv12 stride 满足 64 对齐征程 6E/M/B 是 32 对齐。且输入输出 tensor 的对齐规则也有可能不同。从征程 6E/M/B 迁移征程 6H/P 需要注意金字塔配置文件的 stride 是否满足 64 对齐如果征程 6E/M/B 内存够用的话建议可直接按 64 对齐来申请跨平台迁移时就无需更改配置。若编译时配置了input_no_paddingTrue, output_no_padding boolTrue则无需关心对齐开销若编译时没有打开这两个参数则跨平台编译模型会发现输入输出 tensor 的 stride 参数可能会不同不过部署代码是通过 stride 和 valid_shape 信息来准备/解析数据没有强依赖 stride 的 hard code则也可忽略对齐带来的影响。3.4.3 最小内存单元不同征程 6H/P tensor 最小申请内存是 256 字节征程 6E/M 64 字节征程 6B 128 字节。这个差异会体现在模型的 aligned byte size 属性上对于小于最小内存单元的数据或者不满足最小内存单元整数倍的数据会要求强制对齐。建议输入输出 tensor 内存大小按 aligned byte size 申请不要写 hard code避免迁移时遇到问题。3.4.4 绑核推理征程 6H/P 有两个 dsp 核提交 dsp 任务时可以指定一下 backendhbUCPSchedParam sched_param; HB_UCP_INITIALIZE_SCHED_PARAM(sched_param); sched_param.backend dsp_core_id 0 ? HB_UCP_DSP_CORE_0 : HB_UCP_DSP_CORE_1; sched_param.priority 0; ret hbUCPSubmitTask(task.task_handle, sched_param);征程 6H/P 有三/四个 bpu 核建议所有任务做静态编排后运行时做绑核不建议使用 HB_UCP_BPU_CORE_ANY会因系统调用导致 latency 跳变减少使用抢占等会产生额外 ddr 开销的功能hbUCPSchedParam sched_param; HB_UCP_INITIALIZE_SCHED_PARAM(sched_param); sched_param.backend HB_UCP_BPU_CORE_0; sched_param.priority 200; ret hbUCPSubmitTask(task.task_handle, sched_param);征程 6H/P 单核内支持的抢占策略与征程 6E/M 一致多核已经为编排提供了足够的灵活度建议多核计算平台上尽量避免使用硬件抢占减少抢占引入的额外带宽消耗。4.建议使用流程在征程 6 计算平台上我们建议前期初步做性能评测和性能优化时使用 PTQ 工具只需要准备浮点 onnx 即可较易上手。后续正式做量产迭代使用 QAT 量化工具精度更有保障对于多阶段模型或者模型新增 head 等变化可以更灵活复用已有 QAT 权重有利于模型迭代更新而 PTQ 则无法拼接历史量化 onnx。下图为 PTQ 和 QAT 量化产物对比4.1 性能评测PTQ 环境搭建请参考用户手册-环境部署-Docker 容器部署。4.1.1 快速性能评测默认全 Int8hb_compile --fast-perf --model xxx.onnx --march nash-p需要注意的是fast-perf 默认会删除模型前后的 QuantizeTransposeDequantizeCastReshapeSoftmax 算子如果模型输入输出节点较多会与实际部署性能产生 gap建议按下面的步骤手动修改一下 yaml 文件执行上面那一行命令之后会在当前路径下生成。fast_perf 路径路径下有 yaml 文件打开 yaml 文件按照实际部署需要去掉无需删除的节点一般来说部署时只需要删除量化反量化修改完成后只要模型输入没有变化则后续可一直复用该 yaml 文件修改 onnx_model 路径即可hb_compile -c xxx.yaml4.1.2 int8_fp16 测试{ model_config: { all_node_type: float16 }, op_config: { Conv: { qtype: int8 }, ConvTranspose: { qtype: int8 }, MatMul: { qtype: int8 }, Gemm: { qtype: int8 }, Resize: { qtype: int8 }, GridSample: { qtype: int8 }, GridSamplePlugin: { qtype: int8 } } } // Resize依据经验一般情况下不需要用到fp16精度且fp16速度较慢因此建议默认配置int8 // 公版GridSample和horizon 版本GridSamplePlugin都不支持fp16输入因此需要手动回退int8避免被lower到cpu先生成模版hb_compile --fast-perf --model xxx.onnx --march nash-p默认生成在。fast_perf/隐藏目录下修改 configsed -i s/remove_node_type: .*/remove_node_type: Quantize;Dequantize/ .fast_perf/xxx_config.yaml sed -i s/optimization: run_fast/calibration_type: skip/ .fast_perf/xxx_config.yaml awk /calibration_type: skip/ { print; print quant_config: ./fp16.json; next } 1 .fast_perf/xxx_config.yaml temp.yaml编译hb_compile --config temp.yaml评测其他精度如全 int16softmax/layernorm fp16 等修改上面的 fp16.json 文件即可配置方式详细说明请参考用户手册 - 训练后量化PTQ- quant_config 说明。4.1.3 板端模型性能测试工具进入 OE 包目录samples/ucp_tutorial/tools/hrt_model_exec编译sh build_aarch64.sh将结果文件夹中的 output_shared_J6_aarch64/aarch64/bin/hrt_model_exec 以及 output_shared_J6_aarch64/aarch64/lib 拷贝到板端的{path}下。新建/修改 setup.sh 文件#!/bin/sh #配置hrt_model_exec所在路径 export PATH{path}:${PATH} #配置.so所在路径 export LD_LIBRARY_PATH{path}/lib:${LD_LIBRARY_PATH}执行source setup.sh就可在板子上使用 hrt_model_exec 文件了评测模型延时常用命令hrt_model_exec perf --model_file xxx.hbm --thread_num 1 --frame_count 10004.2 性能分析及优化相较于征程 6E/M征程 6H/P 额外需要考虑的是引入了 FP 计算耗时以及多核的带宽争抢。与平台无关早期评测时建议参考上一章获取模型性能情况后续量产过程中进行精度调试之前也建议先测试一下性能并完成性能优化部分性能优化策略可能数学不等价导致需要重训浮点或 qat。性能分析和优化建议参考如下步骤具体分析和优化过程请见《征程 6 性能分析带宽优化》。4.3 量化训练整个量化训练的过程大致为如下流程改造浮点模型在输入的地方插入 QuantStub输出插入 DequantStub标记模型需要量化的结构calibration 一个 step 后导出 qat.bc确认结构是否完整是否有多余的结构是否有不符合预期的 cpu 节点配置 GEMM 双 int16 其他 float16 做 calibraion调整训练参数fix scale 等直至无限接近浮点。若精度崩掉则先排查流程问题精度达标的情况下若延时也满足预期则量化训练结束配置 GEMM 双 int8 其他 float16 做 calibraion精度不达标的话进入精度 debug 的流程若精度达标则量化训练结束calibration 精度达到浮点 95% 以上还想继续提升精度的话可以进行 qat 训练个别模型 calibration 精度较低可通过 qat 训练得到较大提升测试 quantized.bc 或者 hbm 精度确认是否达标。from horizon_plugin_pytorch.quantization import prepare, QuantStub from torch.quantization import DeQuantStub from horizon_plugin_pytorch.quantization.qconfig_setter import * from horizon_plugin_pytorch.quantization import observer_v2, get_qconfig from horizon_plugin_pytorch.dtype import qint16, qint8 from horizon_plugin_pytorch.march import March, set_march import torch from torchvision.models.mobilenetv2 import MobileNetV2 from horizon_plugin_pytorch.quantization.hbdk4 import export from hbdk4.compiler import save, load, convert, compile class QATReadyMobileNetV2(MobileNetV2): def __init__( self, num_classes: int 10, width_mult: float 1.0, inverted_residual_setting: Optional[List[List[int]]] None, round_nearest: int 8, ): super().__init__( num_classes, width_mult, inverted_residual_setting, round_nearest ) self.quant QuantStub() self.dequant DeQuantStub() def forward(self, x: Tensor) - Tensor: x self.quant(x) x super().forward(x) x self.dequant(x) return x # 1.准备浮点模型 float_model QATReadyMobileNetV2() float_state_dict torch.load(float_ckpt_path) float_model.load_state_dict(float_state_dict) # 2.数据校准 set_march(nash-p) # 在prepare之前设置计算架构 qconfig_template [ ModuleNameTemplate({: torch.float16}), # 全局 feat fp16 MatmulDtypeTemplate( # gemm int8 input input_dtypes[qint8, qint8], ), ConvDtypeTemplate( # gemm int8 input input_dtypeqint8, weight_dtypeqint8, ), ] calibration_qconfig_qconfig_setter QconfigSetter( reference_qconfigget_qconfig(observerobserver_v2.MSEObserver), templatesqconfig_template, enable_optimize True, save_dir ./qconfig, ) calib_model prepare( float_model, example_input, calibration_qconfig_qconfig_setter ) calib_model.eval() set_fake_quantize(calib_model, FakeQuantState.CALIBRATION) calibrate(calib_model) # 评测数据校准精度 calib_model.eval() set_fake_quantize(calib_model, FakeQuantState.VALIDATION) evaluate(calib_model) torch.save(calib_model.state_dict(), calib-checkpoint.ckpt) # 3.量化训练若数据校准精度已达标可跳过该步骤 qat_qconfig_qconfig_setter QconfigSetter( reference_qconfigget_qconfig(observerobserver_v2.MinMaxObserver), templatesqconfig_template, enable_optimize True, save_dir ./qconfig, ) qat_model prepare( float_model, example_input, qat_qconfig_qconfig_setter ) qat_model.load_state_dict(calib_model.state_dict()) qat_model.train() set_fake_quantize(qat_model, FakeQuantState.QAT) train(qat_model) # 评测量化训练精度 qat_model.eval() set_fake_quantize(qat_model, FakeQuantState.VALIDATION) evaluate(qat_model) # 4.模型导出 hbir_qat_model export(qat_model, example_input, namemobilenetv2, input_names(input_name1,input_name2), output_names(output_name1,output_name2), native_pytreeFalse) save(hbir_qat_model, qat.bc) quantized_hbir convert(hbir_qat_model, marchnash-p) # 5.模型编译 compil(quantized_hbir,marchnash-p, pathmodel.hbm)需要注意的是导出 qat.bc 模型时建议指定一下模型输入输出节点名称以及模型名字便于应用集成和后续 trace 分析也避免 hbm 精度评测时同时加载多个名字相同的模型出错。4.3.2 典型量化配置4.3.2.1 基础模版GEMM 双 int8 其他 float16 from horizon_plugin_pytorch.quantization.qconfig_setter import * from horizon_plugin_pytorch.quantization import observer_v2 from horizon_plugin_pytorch.dtype import qint16, qint8 import torch model_qconfig_setter QconfigSetter( reference_qconfigget_qconfig( # 1. 主要用于获取 observer observer( observer_v2.MSEObserver if is_calib else observer_v2.MinMaxObserver ) ), templates[ ModuleNameTemplate({: torch.float16}), # 全局 feat fp16 MatmulDtypeTemplate( # gemm int8 input input_dtypes[qint8, qint8], ), ConvDtypeTemplate( # gemm int8 input input_dtypeqint8, weight_dtypeqint8, ), ], enable_optimize True, save_dir ./qconfig, # qconfig 保存路径有默认值用户可以改 )4.3.2.2 添加 fix scalepyramid 和 resizer 输入请关注部署时模型输入来源为 pyramid 和 resizer 的模型需要输入节点量化精度配置为 int8 类型另外这类输入一般是经过归一化的数值范围在[-11]或者[01]因此建议可以直接设置 fix scale。此外还有一些模型中的节点有明确物理含义建议也手动配置 fix scale避免 qat 过程滑动取平均导致部分有效值域不完整。from horizon_plugin_pytorch.quantization.qconfig_setter import * from horizon_plugin_pytorch.quantization import observer_v2 from horizon_plugin_pytorch.dtype import qint16, qint8 import torch model_qconfig_setter QconfigSetter( reference_qconfigget_qconfig( # 1. 主要用于获取 observer observer( observer_v2.MSEObserver if is_calib else observer_v2.MinMaxObserver ) ), templates[ ModuleNameTemplate({ : torch.float16, backbone.quant: {dtype: qint8, threshold: 1.0}, }), # 全局 feat fp16输入节点配置int8且固定scale1.0/128 MatmulDtypeTemplate( input_dtypes[qint8, qint8], ), ConvDtypeTemplate( # gemm int8 input input_dtypeqint8, weight_dtypeqint8, ), ], enable_optimize True, save_dir ./qconfig, # qconfig 保存路径有默认值用户可以改 )4.3.2.3 通过敏感度增加高精度配置敏感度文件*sensitive_ops.pt生成方式请见下一节-精度调优流程from horizon_plugin_pytorch.quantization.qconfig_setter import * from horizon_plugin_pytorch.quantization import observer_v2 from horizon_plugin_pytorch.dtype import qint16, qint8 import torch model_qconfig_setter QconfigSetter( reference_qconfigget_qconfig( # 1. 主要用于获取 observer observer( observer_v2.MSEObserver if is_calib else observer_v2.MinMaxObserver ) ), templates[ ModuleNameTemplate({: torch.float16}), # 全局 feat fp16 MatmulDtypeTemplate( # gemm int8 input input_dtypes[qint8, qint8], ), ConvDtypeTemplate( # gemm int8 input input_dtypeqint8, weight_dtypeqint8, ), SensitivityTemplate( sensitive_tabletorch.load(debug/output_0_ATOL_sensitive_ops.pt), topk_or_ratio10,# top10敏感节点配置int16 ) ], enable_optimize True, save_dir ./qconfig, # qconfig 保存路径有默认值用户可以改 )4.3.2.4 多阶段模型量化配置若多阶段模型在浮点训练时就是分开训的则 qat 保持和浮点节点一致分为多阶段训练。第一阶段按照前面的配置正常 calib 就好不要 qat除非 calib 精度实在是达标不了qat 之后权重变了二阶段需要 finetune 浮点二阶段使用如下方式将一阶段设置成浮点仅量化二阶段from horizon_plugin_pytorch.quantization.qconfig_setter import * from horizon_plugin_pytorch.quantization import observer_v2 from horizon_plugin_pytorch.dtype import qint16, qint8 import torch stage2 [bev_stage2_vehicle_head.head,bev_stage2_vrumerge_head.head] model_qconfig_setter QconfigSetter( reference_qconfigget_qconfig( # 1. 主要用于获取 observer observer( observer_v2.MSEObserver if is_calib else observer_v2.MinMaxObserver ) ), templates[ ModuleNameTemplate({: torch.float32}), # 全局 feat fp32 ModuleNameTemplate( {n: torch.float16 for n in stage2},# stage2为二阶段模型节点的关键字 ), MatmulDtypeTemplate( # gemm int8 input input_dtypes[qint8, qint8], ), ConvDtypeTemplate( # gemm int8 input input_dtypeqint8, weight_dtypeqint8, ), ], enable_optimize True, save_dir ./qconfig, # qconfig 保存路径有默认值用户可以改 )若想要一阶段和二阶段连接部分的 scale 相同则 qat 阶段不要在两阶段连接部分加量化反量化仅在导出模型时添加class EncoderModule(nn.Module): def __init__(self, ) - None: super().__init__() self.dequant DeQuantStub() self.conv ConvModule(...) def forward(self, input1, input2): input1 self.conv(input1) output input1 input2 if env.get(EXPORT_DEPLOY, 0) 1: return self.dequant(output) return output class DecoderModule(nn.Module): def __init__(self, ) - None: super().__init__() self.quant QuantStub() self.conv ConvModule(...) def forward(self, data): if env.get(EXPORT_DEPLOY, 0) 1: data self.quant(data) data self.conv(data) return data class Model(nn.Module): def __init__(self, ) - None: super().__init__() self.quant1 QuantStub() self.quant2 QuantStub() self.dequant DeQuantStub() self.encoder EncoderModule(...) self.decoder DecoderModule(...) def forward(self, input1, input2): input1 self.quant1(input1) input2 self.quant2(input2) output self.encoder(input1, input2) output self.decoder(output) return self.dequant(output)两阶段分别 calibration 完之后使用如下脚本拼接得到完整的 calibration 权重使用该权重完成后续的 qat 训练若还有第三阶段需要基于二阶段 qat 权重 finetune 浮点stage1 [ backbone, bifpn_neck, bev_stage1_head, ] e2e_stage2 [ task_bev_encoder.bev_quant_stub, task_bev_encoder.bev_encoder.dynamic, e2e_vehicle_head.head, e2e_vrumerge_head.head, ] def filter_ckpt(ckpt, prefix, exclude[]): new_ckpt OrderedDict() new_ckpt[state_dict] OrderedDict() new_ckpt[state_dict]._metadata OrderedDict() for k in ckpt[state_dict].keys(): if any([k.startswith(key) for key in prefix]) and not any([k.startswith(key) for key in exclude]): new_ckpt[state_dict][k] ckpt[state_dict][k] for k in ckpt[state_dict]._metadata.keys(): if any([k.startswith(key) for key in prefix]) and not any([k.startswith(key) for key in exclude]): new_ckpt[state_dict]._metadata[k] ckpt[state_dict]._metadata[k] return new_ckpt def merge_ckpt_func(ckpt_list): new_ckpt OrderedDict() new_ckpt[state_dict] OrderedDict() new_ckpt[state_dict]._metadata OrderedDict() for ckpt in ckpt_list: new_ckpt[state_dict].update(ckpt[state_dict]) new_ckpt[state_dict]._metadata.update(ckpt[state_dict]._metadata) return new_ckpt stage1_ckpt filter_ckpt(torch.load(stage1_calibration_checkpoint_path, map_locationcpu), stage1) e2e_stage2_3_ckpt filter_ckpt(torch.load(e2e_calibration_checkpoint_path, map_locationcpu), e2e_stage2) merge_ckpt merge_ckpt_func([stage1_ckpt, e2e_stage2_3_ckpt]) torch.save(merge_ckpt, merged_stage1-stage2.pth.tar)4.3.3 精度调优流程由于征程 6H/P 上大多数 vector 计算int16 精度和 float16 精度计算速度相当因此建议 vector 计算精度直接使用 float16可有效减小量化调优的难度提升迭代效率。若在其他平台上有全 int8 部署经验或依据经验判断模型全 int8或加少量 int16无精度风险为追求极致帧率可不使用 float16。如下为征程 6H/P 的量化调优建议流程4.3.4 部署模型编译由于模型输入输出格式训练和部署时可能存在区别因此工具链提供了一些 api 用于在量化训练后调整模型以适配部署要求。差异主要是在图像输入格式以及是否需要删除首尾量化反量化节点这两个方面。4.3.4.1 pyramid 或 resizer 输入该操作请在 convert 前完成且 qat 训练时对应输入节点的 quant 需要是 int8 量化。下图为训练和部署编译时模型输入的差异将如下代码加到编译生成 hbm 的流程中只需指定需要修改为 pyramid/resizer 的节点名字即可注意 type 为训练时候的数据格式mean 和 std 也需要结合训练前处理代码做配置from hbdk4.compiler import load model load(qat_model.bc) func model[0] resizer_input [input0] # 部署时数据来源于resizer的输入节点名称列表 pyramid_input [input3] # 部署时数据来源于pyramid的输入节点名称列表 def channge_source(node, input_source, preprocess): mode preprocess[type] if mode rgb: mode yuvbt601full2rgb elif mode bgr: mode yuvbt601full2bgr elif mode yuv: mode None divisor preprocess[divisor] mean preprocess[mean] std preprocess[std] node node.insert_transpose(permutes[0, 3, 1, 2]) print(mode,divisor,mean,std) node node.insert_image_preprocess(modemode, divisordivisor, meanmean, stdstd) if input_source pyramid: node.insert_image_convert(nv12) elif input_source resizer: node.insert_roi_resize(nv12) for input in func.flatten_inputs[::-1]: if input.name in pyramid_input: if input.type.shape[0] 1: split_inputs input.insert_split(0) for split_input in reversed(split_inputs): channge_source(split_input, pyramid, {type:yuv,divisor:1,mean:[128, 128, 128],std:[128, 128, 128]}) elif input.name in resizer_input: if input.type.shape[0] 1: split_inputs input.insert_split(0) for split_input in reversed(split_inputs): channge_source(split_input, resizer, {type:yuv,divisor:1,mean:[128, 128, 128],std:[128, 128, 128]})insert_image_preprocess方法包括以下参数mode可选值包含yuvbt601full2rgbYUVBT601Full 转 RGB 默认yuvbt601full2bgrYUVBT601Full 转 BGRyuvbt601video2rgbYUVBT601Video 转 RGB 模式yuvbt601video2bgrYUVBT601Video 转 BGR 模式bgr2rgbBGR 转 RGBrgb2bgrRGB 转 BGRnone不进行图像格式的转换仅进行 preprocess 处理数据转换除数divisorint 类型默认为 255均值meandouble 类型长度与输入 c 方向对齐默认为 [0.485 0.456 0.406]标准差值stddouble 类型长度与输入 c 方向对齐默认为 [0.229 0.224 0.225]4.3.4.2 算子删除该操作需要在 convert 后完成因为 convert 前模型都还是浮点输入输出没有生成量化反量化节点quantized_model convert(qat_model, march) # remove_io_op会递归删除所有可被删除的节点 quantized_model[0].remove_io_op(op_types [Dequantize,Quantize,Cast,Transpose,Reshape])若进行了删除动作需要在后处理中根据业务需要进行功能补全例如实现量化、反量化的逻辑。量化计算参考代码torch.clamp(torch.round(x/scales), minint16_min, maxint16_max).type(torch.int16)float32_t _round(float32_t input) { std::fesetround(FE_TONEAREST); float32_t result nearbyintf(input); return result; } inline T int_quantize(float32_t value, float32_t scale, float32_t zero_point, float32_t min, float32_t max) { value _round(value / scale zero_point); value std::min(std::max(value, min), max); return static_castT(value); }如果并不想去掉模型所有的量化反量化只想删掉个别输入输出节点相连的 op可采用下面的方法删除与某输入/输出节点直接相连的节点def remove_op_by_ioname(func, io_nameNone): for loc in func.inputs func.outputs: if not loc.is_removable[0]: if io_name loc.name: raise ValueError(fFailed when deleting {io_name} ,which id unremovable) continue attached_op loc.get_attached_op[0] removed None output_name attached_op.outputs[0].name input_name attached_op.inputs[0].name if io_name in [output_name, input_name]: removed, diagnostic loc.remove_attached_op() if removed is True: print(fRemove node {io_name} successfully,flushTrue) if removed is False: raise ValueError( fFailed when deleting {attached_op.name} operator, ferror: {diagnostic}) remove_op_by_ioname(func,_input_0) remove_op_by_ioname(func,_output_0)4.3.5 定点模型精度评测由于 qat 还是伪量化模型从伪量化转换真正的定点模型有可能会产生误差因此建议模型上线之前除了测试 qat torch module 精度之外再测试一下定点模型的精度。定点模型精度可以基于 quantized.bc 或者。hbm 做测试quantized.bc 和 hbm 在模型中无 cpu 算子无 fp32 精度算子的情况下模型输出是二进制一致的。4.3.5.1 quantized.bc 推理pythonquantized.bc 推理输入格式为 dict支持 tensor 和 np.array输出格式与输入一致。当前只支持 cpu 推理建议通过多进程加速推理过程。inputs {inputs[0].name: Y, inputs[1].name: UV} hbir_outputs hbir[0].feed(inputs)C与推理 hbm 接口使用无任何区别便于用户在 x86 端测试系统集成效果具体使用方式请参考后文第五章。so 替换成 x86 的即可。4.3.5.2 hbm 推理由于本地使用 cpu 推理 hbm 速度非常慢因此工具链提供了一个工具方便用户在服务器端给直连的开发板下发推理任务。本文只介绍最简单的单进程使用方式多进程、多阶段模型输入输出传输优化以及统计模型推理、网络传输耗时等请参考用户手册hbm_infer 工具介绍。hbm_model HbmRpcSession( hostxx.xx.xx.xx, local_hbm_pathxx.hbm, #也可传入一个list推理时通过指定model_name来选择推理哪个模型可只传输一次推理所用的.so # core_id2, #绑核推理推理开启L2M模型时建议绑核与环境变量对应 # extra_server_cmdexport HB_DNN_USER_DEFINED_L2M_SIZES0:0:12:0 # L2M模型推理所需环境变量 ) # 打印模型输入输出信息 hbm_model.show_input_output_info() # 准备输入数据 input_data { img: torch.ones((1, 3, 224, 224), dtypetorch.int8) } # 执行推理并返回结果 output_data hbm_model(input_data) # 若传入的是list需要正确指定model_name # output_data hbm_model(input_data, model_namemodel_name) print([output_data[k].shape for k in output_data]) # 关闭server hbm_model.close_server()4.3.5.3 pyramid 输入模型测试建议对于 pyramid 模型由于部署和训练输入格式不一致因此若要使用插入前处理节点后的 quantized.bc 或者 hbm 做精度测试的话需要适配一下前处理代码需要注意的是把训练时的 rgb/bgr/yuv444 转换成 nv12是存在信息损失的若模型训练的时候前处理没有带上转 nv12 的过程则有可能对这样的信息损失不够鲁棒出现掉点的现象。因此若 pyramid 输入定点模型掉点超出预期需要再测试一下不插入前处理节点的模型精度若的确是 nv12 带来的损失建议修改模型前处理重训浮点。如下为将浮点模型输入 data 处理为 deploy 定点模型输入格式的示例代码需要注意的是要修改一下评测前处理只保留读图和 resize 的操作​去掉归一化相关的前处理​这部分通过前文部署模型编译章节的修改动作已经合入到了模型内部def nv12_runtime(data): import cv2 def img2nv12(input_image): image input_image.astype(np.uint8) image image.squeeze(0) image np.transpose(image, (1, 2, 0)) height, width image.shape[0], image.shape[1] # 若读出的图片为BGR格式请做对应修改 yuv420p cv2.cvtColor(image, cv2.COLOR_RGB2YUV_I420).reshape( (height * width * 3 // 2,) ) y yuv420p[: height * width] uv_planar yuv420p[height * width :].reshape((2, height * width // 4)) uv_packed uv_planar.transpose((1, 0)).reshape((height * width // 2,)) return torch.tensor( y.reshape(1, height, width, 1), dtypetorch.uint8 ), torch.tensor( uv_packed.reshape(1, height // 2, width // 2, 2), dtypetorch.uint8 ) dict_data {} for key in data.keys(): if data[key].shape[0] 1: dict_data[f{key}_y], dict_data[f{key}_uv] img2nv12( data[key].cpu().numpy() ) else: for i in range(data[key].shape[0]): ( dict_data[f{key}_{i}_y], dict_data[f{key}_{i}_uv], ) img2nv12(data[key][i : i 1, :, :, :].cpu().numpy()) return dict_data input_6v_deploy nv12_runtime(input_6v_float)5.模型部署5.1 UCP 简介UCPUnify Compute Platform统一计算平台定义了一套统一的异构编程接口 将 SOC 上的功能硬件抽象出来并进行封装对外提供基于功能的 API 进行调用。UCP 提供的具体功能包括​视觉处理​Vision Process、​神经网络模型推理​Neural Network、​高性能计算库​High Performance Library、​自定义算子插件开发​。UCP 支持的 Backbend5.2 模型推理快速上手使用 UCP 推理模型的基本代码参考如下详细信息可参考用户手册《统一计算平台-模型推理开发》、《模型部署实践指导-模型部署实践指导实例》、《UCP 通用 API 介绍》等相关章节。// 1. 加载模型并获取模型名称列表以及Handle { hbDNNInitializeFromFiles(packed_dnn_handle, modelFileName, 1); hbDNNGetModelNameList(model_name_list, model_count, packed_dnn_handle); hbDNNGetModelHandle(dnn_handle, packed_dnn_handle, model_name_list[0]); } // 2. 根据模型的输入输出信息准备张量 std::vectorhbDNNTensor input_tensors; std::vectorhbDNNTensor output_tensors; int input_count 0; int output_count 0; { hbDNNGetInputCount(input_count, dnn_handle); hbDNNGetOutputCount(output_count, dnn_handle); input_tensors.resize(input_count); output_tensors.resize(output_count); prepare_tensor(input_tensors.data(), output_tensors.data(), dnn_handle); } // 3. 准备输入数据并填入对应的张量中 { read_data_2_tensor(input_data, input_tensors); // 确保更新输入后进行Flush操作以确保BPU使用正确的数据 for (int i 0; i input_count; i) { hbUCPMemFlush(input_tensors[i].sysMem, HB_SYS_MEM_CACHE_CLEAN); } } // 4. 创建任务并进行推理 { // 创建任务 hbDNNInferV2(task_handle, output_tensors.data(), input_tensors.data(), dnn_handle) // 提交任务 hbUCPSchedParam sched_param; HB_UCP_INITIALIZE_SCHED_PARAM(sched_param); sched_param.backend HB_UCP_BPU_CORE_ANY; hbUCPSubmitTask(task_handle, sched_param); // 等待任务完成 hbUCPWaitTaskDone(task_handle, 0); } // 5. 处理输出数据 { // 确保处理输出前进行Flush操作以确保读取的不是缓存中的脏数据 for (int i 0; i output_count; i) { hbUCPMemFlush(output_tensors[i].sysMem, HB_SYS_MEM_CACHE_INVALIDATE); } // 对输出进行后处理操作 } // 6. 释放资源 { // 释放任务 hbUCPReleaseTask(task_handle); // 释放输入内存 for (int i 0; i input_count; i) { hbUCPFree((input_tensors[i].sysMem)); } // 释放输出内存 for (int i 0; i output_count; i) { hbUCPFree((output_tensors[i].sysMem)); } // 释放模型 hbDNNRelease(packed_dnn_handle); }5.3 Pyramid/Resizer 模型输入准备说明由于 Pyramid/Resizer 模型相对特殊其输入是动态 shape/stride因此单独介绍一下其输入 tensor 准备的注意事项和技巧。下表是解析 Pyramid/Resizer 模型观察到的现象-1 为占位符表示为动态Pyramid 输入的 stride 为动态Resizer 输入的 H、W、stride 均为动态。) :Resizer 输入的 ​HW 动态​是因为原始输入的大小可以是任意的Pyramid/Resizer 输入的​​ stride 动态​可以理解为是支持Crop 功能后文 5.4.1 节hrt_model_exec model_info板端可执行程序工具在征程 6H/P 平台上要求 Pyramid/Resizer 输入必须满足 W64 对齐因此无论是金字塔配置还是模型输入准备都需要满足对齐要求。输入 tensor 准备#define ALIGN(value, alignment) (((value) ((alignment)-1)) ~((alignment)-1)) #define ALIGN_64(value) ALIGN(value, 64) int prepare_image_tensor(const std::vectorhbUCPSysMem image_mem, int input_h, int input_w, hbDNNHandle_t dnn_handle, std::vectorhbDNNTensor input_tensor) { // 准备Y、UV输入tensor for (int i 0; i 2; i) { HB_CHECK_SUCCESS(hbDNNGetInputTensorProperties(input_tensor[i].properties, dnn_handle, i), hbDNNGetInputTensorProperties failed); // auto w_stride ALIGN_64(input_w); // int32_t y_mem_size input_h * w_stride; input_tensor[i].sysMem[0] image_mem[i]; // 配置原图大小NHWC input_tensor[i].properties.validShape.dimensionSize[1] input_h; input_tensor[i].properties.validShape.dimensionSize[2] input_w; if (i 1) { // UV输入大小为Y的1/2 input_tensor[i].properties.validShape.dimensionSize[1] / 2; input_tensor[i].properties.validShape.dimensionSize[2] / 2; } // stride满足64对齐 input_tensor[i].properties.stride[1] ALIGN_64(input_tensor[i].properties.stride[2] * input_tensor[i].properties.validShape.dimensionSize[2]); input_tensor[i].properties.stride[0] input_tensor[i].properties.stride[1] * input_tensor[i].properties.validShape.dimensionSize[1]; } return 0; } // 准备roi输入tensor int prepare_roi_tensor(const hbUCPSysMem *roi_mem, hbDNNHandle_t dnn_handle, int32_t roi_tensor_id, hbDNNTensor *roi_tensor) { HB_CHECK_SUCCESS(hbDNNGetInputTensorProperties(roi_tensor-properties, dnn_handle, roi_tensor_id), hbDNNGetInputTensorProperties failed); roi_tensor-sysMem[0] *roi_mem; return 0; } int prepare_roi_mem(const std::vectorhbDNNRoi rois, std::vectorhbUCPSysMem roi_mem) { auto roi_size rois.size(); roi_mem.resize(roi_size); for (auto i 0; i roi_size; i) { int32_t mem_size 4 * sizeof(int32_t); HB_CHECK_SUCCESS(hbUCPMallocCached(roi_mem[i], mem_size, 0), hbUCPMallocCached failed); int32_t *roi_data reinterpret_castint32_t *(roi_mem[i].virAddr); roi_data[0] rois[i].left; roi_data[1] rois[i].top; roi_data[2] rois[i].right; roi_data[3] rois[i].bottom; hbUCPMemFlush(roi_mem[i], HB_SYS_MEM_CACHE_CLEAN); } return 0; }金字塔配置{ ds_roi_layer: 2, ds_roi_sel: 1, ds_roi_start_top: 0, ds_roi_start_left: 0, ds_roi_region_width: 480, ds_roi_region_height: 256, ds_roi_wstride_y: 512, // 480不满足64对齐要求 ds_roi_wstride_uv: 512, // 480不满足64对齐要求 ds_roi_out_width: 480, ds_roi_out_height: 256 }5.4 模型部署优化5.4.1 通过地址偏移完成 crop场景描述Y: validShape (1,224,224,1), stride (-1,-1,1,1)UV: validShape (1,112,112,2), stride (-1,-1,2,1)该模型的输入图片大小为 224x224假设有一张 H x W 376 x 384其中 W 存在大小为 8 的 padding因为 nv12 需要 W64 对齐的图片可以直接基于 stride 值进行 crop没有额外的拷贝开销Crop 功能使用原始图片 Y、UV 的 validShape、stride、指针如下Y: validShape 1 376 384 1 stride 384*376 384 1 1内存指针为y_dataUV: validShape 1 188 192 2 stride 384*188 384 2 1内存指针为uv_data模型输入张量准备-YCrop 起始点 [h w] [50 64]则 地址偏移为y_offset50*384​​ 64*1内存指针为y_datay_offset模型输入应设置为validShape(1,224,224,1)stride (224*384,384,1,1)模型输入张量准备-UV由于 UV 尺寸为 Y 的 1/2因此裁剪起始点为 [25 32]则 地址偏移为uv_offset25*384​​ 32*2内存指针为uv_datauv_offset模型输入应设置为validShape(1,112,112,2)stride (112*384,384,2,1)Crop 限制条件图像要求分辨率 ≥ 模型输入w_stride需要 64 字节对齐模型要求输入 validShape 为固定值stride 为动态值这样能通过控制 stride 的大小对图像进行 Crop裁剪位置由于裁剪是对图像内存进行偏移而对于输入内存的首地址要求 64 对齐​示例​ucp_tutorial/dnn/basic_samples/code/02_advanced_samples/crop/src/main.cc5.4.2 小模型批处理由于 BPU 是资源独占式硬件所以对于 Latency 很小的模型而言其框架调度开销占比会相对较大。在 征程 6 平台UCP 支持通过复用 task_handle 的方式将多个小模型任务一次性下发全部执行完成后再一次性返回从而可将 N 次框架调度开销合并为 1 次以下为参考代码// 获取模型指针并存储 std::vectorhbDNNHandle_t model_handles; // 准备各个模型的输入输出准备过程省略 std::vectorstd::vectorhbDNNTensor inputs; std::vectorstd::vectorhbDNNTensor outputs; // 创建任务并进行推理 { // 创建并添加任务复用task_handle hbUCPTaskHandle_t task_handle{nullptr}; for(size_t task_id{0U}; task_id inputs.size(); task_id){ hbDNNInferV2(task_handle, outputs[task_id].data(), inputs[task_id].data(), model_handles[i]); } // 提交任务 hbUCPSchedParam sche_param; HB_UCP_INITIALIZE_SCHED_PARAM(sche_param); sche_param.backend HB_UCP_BPU_CORE_ANY; hbUCPSubmitTask(task_handle, sche_param); // 等待任务完成 hbUCPWaitTaskDone(task_handle, 0); }5.4.3 优先级调度/抢占UCP 支持任务优先级调度和抢占可通过hbUCPSchedParam结构体进行配置其中prioritycustomId submit_time任务提交时间priority支持 [0 255]对于模型任务而言[0 253] 为普通优先级不可抢占其他任务但在未执行时支持按优先级进行排队254 为 high 抢占任务可支持抢占普通任务255 为 urgent 抢占任务可抢占普通任务和 high 抢占任务可被中断抢占的低优任务需要在模型编译阶段配置max_time_per_fc参数拆分模型指令其他 backend 任务priority 支持 [0 255]但不支持抢占可以认为都是普通优先级5.5 DSP 开发为了简化用户开发UCP 封装了一套基于 RPC 的开发框架来实现 CPU 对 DSP 的功能调用但具体 DSP 算子实现仍是调用 Cadence 接口去做开发。总体来说可分为三个步骤使用 Cadence 提供的工具及资料完成算子开发int test_custom_op(void *input, void *output, void *tm) { // custom impl return 0; }DSP 侧通过 UCP 提供的 API 注册算子编译带自定义算子的镜像// dsp镜像中注册自定义算子 hb_dsp_register_fn(cmd, test_custom_op, latency)ARM 侧通过 UCP 提供的算子调用接口完成开发板上的部署使用。// 将输入输出的hbUCPSysMem映射为DSP可访问的内存地址 hbUCPSysMem in; hbUCPMalloc(in, in_size, 0) hbDSPAddrMap(in, in) hbUCPSysMem out; hbUCPMalloc(out, out_size, 0) hbDSPAddrMap(out, out) // 创建并提交DSP任务 hbUCPTaskHandle_t taskHandle{nullptr}; hbDSPRpcV2(taskHandle, in, out, cmd) hbUCPSchedParam ctrl_param; HB_UCP_INITIALIZE_SCHED_PARAM(ctrl_param); ctrl_param.backend HB_UCP_DSP_CORE_ANY; hbUCPSubmitTask(task_handle, ctrl_param); // 等待任务完成 hbUCPWaitTaskDone(task_handle, 0);更多信息可见用户手册《统一计算平台-自定义算子-DSP 算子开发》。6. 相关基础知识若需要了解 nv12 输入格式模型量化等基础知识可以在开发者社区《地平线算法工具链社区资源整合》https://developer.horizon.auto/blog/10364搜索。
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

网站构架图建设公司网站需要注意哪些

阿里开源视频大模型Wan2.1-VACE横空出世 重构视频创作产业生态 【免费下载链接】Wan2.1-VACE-14B 项目地址: https://ai.gitcode.com/hf_mirrors/Wan-AI/Wan2.1-VACE-14B 2025年5月15日,阿里巴巴集团正式对外发布视频生成与编辑领域的突破性成果——Wan2.1-…

张小明 2026/1/7 13:44:58 网站建设

手机营销网站模板免费下载企业官网wordpress主题

多智能体系统优化:创新任务分配与动态资源管理策略 【免费下载链接】awesome-ai-agents A list of AI autonomous agents 项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-ai-agents 作为分布式人工智能的核心技术,多智能体系统优化正…

张小明 2026/1/7 13:44:54 网站建设

合肥红酒网站建设招商网代理

如何快速上手ComfyUI视频生成?完整配置指南 【免费下载链接】ComfyUI-WanVideoWrapper 项目地址: https://gitcode.com/GitHub_Trending/co/ComfyUI-WanVideoWrapper 想要在ComfyUI中体验专业的视频生成功能吗?ComfyUI-WanVideoWrapper正是您需要…

张小明 2026/1/7 13:44:51 网站建设

郑州做网站 码通菏泽网站建设兼职

1 启发式测试的本质与价值 在瞬息万变的软件开发周期中,测试人员常面临测试时间不足、需求模糊或测试覆盖率难以量化等挑战。启发式测试(Heuristic Testing)作为一种基于经验认知的测试方法论,通过结构化的问题解决模式&#xff…

张小明 2026/1/7 13:44:48 网站建设

工程项目查询哪个网站上海网页设计公司怎么样

OpenWrt网络加速革命:4大核心技术让你的路由器性能翻倍 【免费下载链接】turboacc 一个适用于官方openwrt(22.03/23.05/24.10) firewall4的turboacc 项目地址: https://gitcode.com/gh_mirrors/tu/turboacc 你是否曾经在游戏中遭遇网络延迟,或者在…

张小明 2026/1/7 13:44:44 网站建设

企业网站建设应具备的功能html5网页制作课程

问题:公司的小程序商品详情页出现了图片之间出现白色间隙的问题 分析: 我们设置的图片是inline-block,因为还是有inline的成分,inline元素默认是baseline对齐的。当baseline对齐的时候 下方会有4px 的空隙。 解决: vertical-a…

张小明 2025/12/29 16:57:01 网站建设