cann/asc-devkit算子入图开发基本流程
基本开发流程【免费下载链接】asc-devkit本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言原生支持C和C标准规范主要由类库和语言扩展层构成提供多层级API满足多维场景算子开发诉求。项目地址: https://gitcode.com/cann/asc-devkit该开发流程以工程化算子开发为基础除了需要提供工程化算子开发中的算子实现文件外还需要额外交付算子入图的代码文件。本节仅提供算子入图代码文件的开发指导。假设下图是我们需要使用的网络模型您可能会想直接逐个算子调用根据输入tensor得到输出tensor就可以完成网络的运行但在图模式场景下实际的网络模型生成过程中会先进行tensor shape以及datatype的推导。这样可以让我们在图执行之前就知道各tensor的数据类型和形状提前校验其正确性同时提前推理出算子的输出张量描述包括张量的形状、数据类型及数据排布格式等信息算子构图准备阶段就可以为所有的张量静态分配内存避免动态内存分配带来的开销。下面的网络模型经过shape和datatype推导之后可以得到灰色底纹框中的推导信息图1shape与datatype推导示意图![](https://raw.gitcode.com/cann/asc-devkit/raw/c975c4a444b8d5c5dfc5201203324866d8a916d3/docs/zh/guide/figures/shape与datatype推导示意图.png shape与datatype推导示意图?utm_sourcegitcode_repo_files)除了tiling实现外算子入图时需要额外提供的实现代码有以下几种datatype推导根据算子的输入datatype、算子逻辑及算子属性等信息推理出算子的输出张量datatype。shape推导根据算子的输入shape、算子逻辑及算子属性等信息推理出算子的输出张量shape。ShapeRange推导编译时无法推导输出shape只能推导输出shape range执行完才能得出输出shape。在下发时需要按照输出shape range来申请最大输出内存该类算子需要提供ShapeRange推导函数。声明数据依赖部分算子在InferShape时需要依赖某个输入的具体值才可以进行这类算子被称为“数据依赖算子”对应的输入被称为“数据依赖输入”。该类算子在注册时需要声明其数据依赖输入。下表列出了不同类型的算子对上述实现代码的要求。表1不同的类型的算子对入图实现代码的要求分类对入图实现代码的要求根据输入shape可以推导出输出shape。shape推导datatype推导依赖输入的value才能推导出输出shape即数据依赖算子。如Reshape算子依赖shape输入的value才能推导出输出shape。shape推导datatype推导声明数据依赖编译时无法推导输出shape只能推导输出shape range执行完才能得出输出shape。Shape推导必选DataType推导必选ShapeRange推导必选声明数据依赖按需实际开发时通过固定的datatype和shape推导原型实现推导函数然后再通过SetInferShape、SetInferDataType接口来关联对应的shape推导函数样例如下。namespace ge { static graphStatus InferShape(gert::InferShapeContext *context) { ... return GRAPH_SUCCESS; } static graphStatus InferDataType(gert::InferDataTypeContext *context) { ... return ge::GRAPH_SUCCESS; } } // namespace ge namespace ops { class AddCustom : public OpDef { public: AddCustom(const char* name) : OpDef(name) { this-Input(x) .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32}) .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); this-Input(y) .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32}) .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); this-Output(z) .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32}) .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); // 根据用户的算子调用方式决定需不需要注册图模式调用方式下需要 this-SetInferShape(ge::InferShape); this-SetInferShapeRange(ge::InferShapeRange); this-SetInferDataType(ge::InferDataType); this-AICore() .SetTiling(optiling::TilingFunc); // 请替换为实际的昇腾AI处理器型号 this-AICore().AddConfig(ascendxxx); } }; OP_ADD(AddCustom); } // namespace opsdatatype推导以AddCustom算子为例InferDataType的实现如下所示。该样例中输出tensor的数据类型与输入tensor的数据类型相同所以直接将任意一个输入tensor的数据类型赋给输出tensor即可。namespace ge { static graphStatus InferDataType(gert::InferDataTypeContext* context) { const auto inputDataType context-GetInputDataType(0); context-SetOutputDataType(0, inputDataType); return ge::GRAPH_SUCCESS; } } // namespace ge如下示例则给出了更灵活的datatype推导样例当输入的数据类型为DT_INT4时其输出的数据类型为DT_INT32。ge::graphStatus InferDataTypeForFoo(gert::InferDataTypeContext* context) { if (context-GetInputDataType(0) DT_INT4) { context-SetOutputDataType(0, DT_INT32); } }shape推导简单的shape推导逻辑可以使用Follow接口来表达比如输出shape和输入shape相同的情况。示例如下输出“y1”Follow输入“x1”场景指定Follow模式为SHAPE此时“y1”的shape将会和“x1”保持一致。this-Input(x1) .ParamType(REQUIRED) .DataType({ge::DT_FLOAT, ge::DT_FLOAT}) .Format({ge::FORMAT_ND, ge::FORMAT_ND}); this-Input(x2) .ParamType(REQUIRED) .DataType({ge::DT_FLOAT, ge::DT_FLOAT}) .Format({ge::FORMAT_ND, ge::FORMAT_ND}); this-Output(y1) .ParamType(REQUIRED) .DataType({ge::DT_FLOAT, ge::DT_FLOAT}) .Format({ge::FORMAT_ND, ge::FORMAT_ND}) .Follow(x1, FollowType::SHAPE);无法在原型定义中通过Follow表达的情况需要开发者编写InferShape函数InferShape函数的原型是固定的如下示例接受一个InferShapeContext作为输入从此context上可以获取到输入、输出的shape指针等内容。输入shape为const类型因此InferShape时输入shape是只读、不允许修改的。InferShape成功后返回ge::GRAPH_SUCCESS其他返回值被认为推导失败。推导失败后执行过程结束退出。以ReShape算子为例InferShape的实现如下所示。根据第1个输入shape输入的值Reshape算子将第0个输入x输入的shape做变换并输出到其第0个输出y输出上。Reshape的InferShape实现为ge::graphStatus InferShapeForReshape(InferShapeContext *context) { const gert::Shape *x_shape context-GetInputShape(0); // 获取第0个输入的shape const gert::Tensor *shape_tensor context-GetInputTensor(1); // 获取第1个输入的tensor gert::Shape *output_shape context-GetOutputShape(0); if (x_shape nullptr || shape_tensor nullptr || output_shape nullptr) { // 防御式编程不应该出现的场景打印错误并返回失败 return ge::GRAPH_FAILED; } auto reshape_size static_castint32_t(shape_tensor-GetShapeSize()); if (reshape_size 1) { // 防御式编程不应该出现的场景打印错误并返回失败 return ge::GRAPH_FAILED; } // 根据原型信息Reshape的shape输入支持INT32与INT64两类根据不同的类型进入对应的模板函数中做真正的shape变换操作 if (shape_tensor-GetDataType() ge::DT_INT32) { int32_t *reshape_data shape_tensor-GetDataint32_t(); return ReshapeInferShapeImplint32_t(reshape_data, *x_shape, *output_shape, reshape_size); } else { int64_t *reshape_data shape_tensor-GetDataint64_t(); return ReshapeInferShapeImplint64_t(reshape_data, *x_shape, *output_shape, reshape_size); } }InferShapeContext public继承自ExtendedKernelContext因此ExtendedKernelContext中提供的方法如获取算子type、name、属性等接口均可以在InferShapeContext实例中调用。[!CAUTION]注意InferShape推导函数和Follow接口不能混用即不支持部分输出采用Infershape推导、部分输出采用Follow推导的情况。若用户同时使用了InferShape函数和Follow接口以用户的InferShape函数为准需要保证在InferShape函数中能够推导出所有的输出shape。为了效率考虑调用InferShape函数时框架不会为输出shape做初始化因此在InferShape函数中可以认为输出是未初始化的状态。如果在InferShape时希望通过Append方式操作输出shape需要先将输出shape的DimNum清零以防止出现未定义行为。InferShapeRange实现某些算子的输出Shape在计算完成后才能确定。比如unique算子其Shape的推导逻辑如下给定一维Tensor x找到其中不重复的元素返回去重后的Tensor y输出idx与输入x大小相同保存x每个元素在y中的索引。# tensor x is [1, 1, 2, 4, 4, 4, 7, 8, 8] x shape[9] y, idx unique(x) y [1, 2, 4, 7, 8] y shape[5] idx [0, 0, 1, 2, 2, 2, 3, 4, 4] idx shape[9]由此可知y的shape在编译时为[-1]unique执行后shape才确定。在入图场景执行时需要在执行前分配输出内存而内存的大小依赖于输出Shape和数据类型。对于此类算子由于输出Shape在执行后才能确定因此需要根据输出Shape的范围按照最大范围申请输出内存以确保有足够的空间供计算函数写入输出Tensor。这种场景下开发者需要自行实现InferShapeRange函数来推导输出Shape的范围。下面以unique算子为例子介绍InferShapeRange函数的实现方法。ge::graphStatus UniqueInferShapeRangeFunc(gert::InferShapeRangeContext *context) { // 取输入的shape range auto x_shape_range context-GetInputShapeRange(0U); OPS_CHECK_NULL_WITH_CONTEXT(context, x_shape_range); OPS_CHECK_NULL_WITH_CONTEXT(context, x_shape_range-GetMax()); OPS_CHECK_NULL_WITH_CONTEXT(context, x_shape_range-GetMin()); // 开始计算y输出的shape range auto y_shape_range context-GetOutputShapeRange(0U); OPS_CHECK_NULL_WITH_CONTEXT(context, y_shape_range); y_shape_range-GetMax()-SetDimNum(1); // 一维向量rank为1 y_shape_range-GetMin()-SetDimNum(1); auto x_max_shape x_shape_range-GetMax(); auto x_shape_dimnum x_max_shape-GetDim(0); // x为一维Tensor其shape为[n] x_shape_dimnum表示x输入的元素个数n if (x_shape_dimnum 1) { // 若x输入只有1个元素不存在去重y的shape轴最小最大均为1. 因此range为[1~1] y_shape_range-GetMax()-SetDim(0, 1); y_shape_range-GetMin()-SetDim(0, 1); } else { // 若x输入有0个元素或者大于1个元素去重后y的元素个数最小为x的min最大为x的max y_shape_range-GetMax()-SetDim(0, x_shape_dimnum); y_shape_range-GetMin()-SetDim(0, x_shape_range-GetMin()); } // 开始计算输出idx的shape range // 输出idx表示x元素在y中的索引其元素个数与x相等因此shape range与x一致 auto idx_shape_range context-GetOutputShapeRange(1U); OPS_CHECK_NULL_WITH_CONTEXT(context, idx_shape_range); *(idx_shape_range-GetMax()) *(x_shape_range-GetMax()); *(idx_shape_range-GetMin()) *(x_shape_range-GetMin()); return ge::GRAPH_SUCCESS; }InferShape时获取属性、输入在InferShape、Tiling时可以通过context实例获取算子IR属性值所谓IR属性是指在IR注册时定义的属性以TransData算子为例namespace ops { class TransData : public OpDef { public: explicit TransData(const char *name) : OpDef(name) { this-Input(src) ... this-Output(dst) ... this-Attr(src_format) .AttrType(REQUIRED) .String(); this-Attr(dst_format) .AttrType(REQUIRED) .String(); this-Attr(group) .AttrType(OPTIONAL) .Int(1); ... } }; OP_ADD(TransData); } // namespace ops其原型定义中声明了src_format、dst_format、group三个属性可以通过如下方式获取算子属性ge::graphStatus ExampleGetTransDataAttr(TilingContext *context) { // 获取所有属性 const RuntimeAttrs *attrs context-GetAttrs(); ASSERT_NOT_NULL(attrs); // 按照在原型定义中的顺序使用index获取属性index从0开始计数 const char *src_format attrs-GetAttrPointerchar(0); // 获取src_formatsrc_format是第一个属性因此index为0 const char *dst_format attrs-GetAttrPointerchar(1); // 获取dst_formatdst_format是第二个属性因此index为1 const int64_t group attrs-GetAttrPointerint64_t(2); // 获取groupgroup是第三个属性因此index为2 return ge::GRAPH_SUCCESS; }通过index而不是字符串name来索引输入输出对于带有OPTIONAL、DYNAMIC类型输入的算子可能出现实例化后单纯通过index无法索引到具体输入的问题以DynamicRNNV3算子为例namespace ops { class DynamicRNNV3 : public OpDef { public: explicit DynamicRNNV3(const char *name) : OpDef(name) { this-Input(x) .ParamType(REQUIRED) ... this-Input(w) .ParamType(REQUIRED) ... this-Input(b) .ParamType(REQUIRED) ... this-Input(seq_length) .ParamType(OPTIONAL) ... this-Input(init_h) .ParamType(OPTIONAL) ... this-Input(init_c) .ParamType(OPTIONAL) ... this-Input(wci) .ParamType(OPTIONAL) ... this-Input(wcf) .ParamType(OPTIONAL) ... this-Input(mask) .ParamType(OPTIONAL) ... this-Input(mask) .ParamType(OPTIONAL) ... this-Input(project) .ParamType(OPTIONAL) ... ... } }; OP_ADD(DynamicRNNV3); } // namespace ops由于DynamicRNNV3算子有连续的多个optional输入这导致init_h及其后面的输入的实例化后index都是不确定的对于这种类型的算子可以通过GetOptionalInputShape传入原型对应的index来获取对应的输入shape等数据以InferShape为例ge::graphStatus InferShapeForDynamicRNNV3(InferShapeContext *context) { // 对于前两个输入不受到optional或dynamic的影响可以按照常规方法获取输入shape auto x_shape context-GetInputShape(0); auto w_shape context-GetInputShape(1); if (x_shape nullptr || w_shape nullptr) { return ge::GRAPH_FAILED; } int64_t state_size 0; // 在原型定义上project是第11个输入(从0开始计数) constexpr int64_t kProjectInputIndex 11; // 受到前面optional输入影响的project实例化后输入的index是不确定的通过GetOptionalInputShape来获取对应的输入shape // GetOptionalInputShape的入参为原型上对应的index auto project_shape context-GetOptionalInputShape(kProjectInputIndex); if (project_shape ! nullptr) { if (project_shape-GetDimNum() 2) { return ge::GRAPH_FAILED; } state_size project_shape-GetDim(1); } // 更多的infershape逻辑... return ge::GRAPH_SUCCESS; }对于dynamic类型的输入实例化后的输入可能是一到多个对于此类输入获取方式为// ir_index此输入在原型定义中的index从0开始计数 // relative_index该输入实例化后的相对index从0开始计数例如某个DYNAMIC_INPUT实例化了3个要取第二个那么relative_index 1 auto shape context-GetDynamicInputShape(ir_index, relative_index);本节举例的获取optional、dynamic输入的方式在InferShape、Tiling函数中均可以调用。数据依赖一般来说具备输入shape后算子可以通过InferShape推导出输出shape。然而部分算子在InferShape时需要依赖某个输入的具体值才可以进行这类算子被称为“数据依赖算子”对应的输入被称为“数据依赖输入”。以Reshape算子为例其依据shape输入的描述对输入的shape做调整因此Reshape算子依赖shape输入的值。这类算子需要在原型定义时通过ValueDepend接口声明对应的输入为数据依赖输入。namespace ops { class Reshape : public OpDef { public: explicit Reshape(const char *name) : OpDef(name) { ... this-Input(shape) .ParamType(REQUIRED) ... .ValueDepend(REQUIRED) // 声明ReShape算子的shape输入为数据依赖输入 ... } }; OP_ADD(Reshape); } // namespace ops根据第1个输入shape输入的值Reshape算子将第0个输入x输入的shape做变换并输出到其第0个输出y输出上。Reshape的InferShape实现为// shape变换具体实现 templatetypename T ge::graphStatus ReshapeInferShapeImpl(const T *reshape_dims, const gert::Shape x_shape, gert::Shape output_shape, int32_t reshape_rank) { constexpr T UNKNOWN_DIM -1; // 将算子输出的维度数设置为reshape后的维度数reshape_rank output_shape.SetDimNum(reshape_rank); auto x_shape_size x_shape.GetShapeSize(); int64_t output_shapesize 1; size_t unknown_dim_idx std::numeric_limitssize_t::max(); for (int32_t i 0; i reshape_rank; i) { if (reshape_dims[i] ! UNKNOWN_DIM) { // reshape后某一轴的维度值不为-1 output_shape.SetDim(i, reshape_dims[i]); // 设置输出的维度值为reshape后的维度值 output_shapesize * reshape_dims[i]; // 计算当前输出元素数量 } else { output_shape.SetDim(i, 1); // reshape后某一轴的维度值为-1临时设置输出的维度值为1后续计算后看是否可以推导出确定值并记录未知维度的索引 unknown_dim_idx i; } } if (unknown_dim_idx std::numeric_limitssize_t::max() output_shapesize x_shape_size) { return ge::GRAPH_SUCCESS; // 不存在未知维度且输出shape size和输入x的shape size一致直接返回成功 } else if (unknown_dim_idx ! std::numeric_limitssize_t::max() x_shape_size % output_shapesize 0) { output_shape.SetDim(unknown_dim_idx, x_shape_size / output_shapesize); // 存在未知维度根据输入shape动态调整未知维度值保持总元素个数不变 return ge::GRAPH_SUCCESS; } return ge::GRAPH_FAILED; } ge::graphStatus InferShapeForReshape(InferShapeContext *context) { const gert::Shape *x_shape context-GetInputShape(0); // 获取第0个输入的shape const gert::Tensor *shape_tensor context-GetInputTensor(1); // 获取第1个输入的tensor gert::Shape *output_shape context-GetOutputShape(0); if (x_shape nullptr || shape_tensor nullptr || output_shape nullptr) { // 防御式编程不应该出现的场景打印错误并返回失败 return ge::GRAPH_FAILED; } auto reshape_size static_castint32_t(shape_tensor-GetShapeSize()); if (reshape_size 1) { // 防御式编程不应该出现的场景打印错误并返回失败 return ge::GRAPH_FAILED; } // 根据原型信息Reshape的shape输入支持INT32与INT64两类根据不同的类型进入对应的模板函数中做真正的shape变换操作 if (shape_tensor-GetDataType() ge::DT_INT32) { int32_t *reshape_data shape_tensor-GetDataint32_t(); return ReshapeInferShapeImplint32_t(reshape_data, *x_shape, *output_shape, reshape_size); } else { int64_t *reshape_data shape_tensor-GetDataint64_t(); return ReshapeInferShapeImplint64_t(reshape_data, *x_shape, *output_shape, reshape_size); } }[!CAUTION]注意只有声明过数据依赖的输入才可以在InferShape时调用GetInputTensor等获取tensor的接口获取其对应的tensor数据。若对一个未声明数据依赖的输入调用GetInputTensor等获取tensor的接口只能在tensor中获取到正确的shape、format、datatype信息无法获取到真实的tensor数据地址获取到的地址为nullptr。从tensor中获取tensor_data时(GetDataint32_t或GetDataint64_t)使用者需要保证获取的数据类型是正确的否则行为是未定义的。【免费下载链接】asc-devkit本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言原生支持C和C标准规范主要由类库和语言扩展层构成提供多层级API满足多维场景算子开发诉求。项目地址: https://gitcode.com/cann/asc-devkit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

ST-LINK_V2调试器核心功能与SWD接口实战指南

ST-LINK_V2调试器核心功能与SWD接口实战指南

1. ST-LINK_V2调试器核心功能解析ST-LINK_V2是STMicroelectronics官方推出的ARM Cortex-M系列微控制器调试编程工具,作为嵌入式开发者的"手术刀",它能精准完成芯片级调试、程序烧录和性能分析。与早期版本相比,V2版本在SWD协议支持…

2026/10/19 11:52:41 阅读更多 →
如何高效使用AutoHotkey实现谷歌浏览器自动化:专业实战指南

如何高效使用AutoHotkey实现谷歌浏览器自动化:专业实战指南

如何高效使用AutoHotkey实现谷歌浏览器自动化:专业实战指南 【免费下载链接】Chrome.ahk Automate Google Chrome using native AutoHotkey 项目地址: https://gitcode.com/gh_mirrors/ch/Chrome.ahk Chrome.ahk是一款强大的AutoHotkey谷歌浏览器自动化库&am…

2026/10/20 15:09:48 阅读更多 →
阿里二面挂了!被问“RAG效果不好怎么评估”,我答凭感觉,面试官:你搁这玄学炼丹呢?

阿里二面挂了!被问“RAG效果不好怎么评估”,我答凭感觉,面试官:你搁这玄学炼丹呢?

优化 RAG 系统时调参全凭感觉?本文带你走出“玄学评估”的泥潭。从核心的 RAG 三角评估模型(RAG Triad) 到 LLM-as-a-Judge 的成本陷阱,再到将 RAGAS 接入 CI/CD 流水线的降维打击。手把手教你搭建一套可量化、自动化的 AI 工程防…

2026/10/16 19:45:50 阅读更多 →

最新新闻

鸿蒙ArkTS生鲜超市实战项目解析与避坑指南

鸿蒙ArkTS生鲜超市实战项目解析与避坑指南

简介:本资源是一套基于华为鸿蒙操作系统(HarmonyOS)开发的生鲜超市行业应用实战项目,面向Android开发者转型鸿蒙生态、高校学生学习分布式应用开发及企业级HarmonyOS项目实践者。项目聚焦线上线下融合购物场景,完整呈现…

2026/10/20 20:58:45 阅读更多 →
Chrome链接总是覆盖当前页?教你设置自动在新标签页打开

Chrome链接总是覆盖当前页?教你设置自动在新标签页打开

"每次点链接都把当前页覆盖掉,后来越来越烦"——这个需求我收到过太多次了。Chrome 默认点击链接就在当前标签页打开,想在新标签页打开,要么按着 Ctrl 点,要么右键菜单选"在新标签页中打开链接"。一次两次还行…

2026/10/20 20:58:45 阅读更多 →
LMDeploy实战:开源大模型量化、推理与OpenAI兼容服务部署

LMDeploy实战:开源大模型量化、推理与OpenAI兼容服务部署

这段时间一直在折腾开源大模型的落地部署,试了不少推理框架,最后在业务环境里稳定跑下来的是 LMDeploy。如果你也在做类似的事情——想把 Qwen、Llama 这类开源模型接到自己的系统里,或者想在项目里做一个带 API 的聊天服务,那么这…

2026/10/20 20:58:45 阅读更多 →
Windows cmd进入指定目录全攻略:cd命令、跨盘符与批处理技巧

Windows cmd进入指定目录全攻略:cd命令、跨盘符与批处理技巧

Windows 下在 cmd 里进指定目录,这事看起来简单到不值一提,但实际操作中我见过太多人在这一步卡住——输入cd D:\某文件夹没反应,路径一长就报错,带空格的目录怎么都进不去,还有一个很经典的cd后提示「不是内部或外部命…

2026/10/20 20:58:45 阅读更多 →
LMDeploy 大模型部署实战:从模型量化到API服务全流程指南

LMDeploy 大模型部署实战:从模型量化到API服务全流程指南

之前帮某开发者的“模拟项目X”做大模型部署时,我在 GPU 服务器上对比过好几套推理方案,踩完一圈坑之后,现在手里的长期方案基本固定成了 LMDeploy。如果你手里已经有一个开源对话模型,正打算放进业务里做 API 服务,或…

2026/10/20 20:58:45 阅读更多 →
TensorFlow鞋类识别实战:Fashion MNIST图像分类全流程

TensorFlow鞋类识别实战:Fashion MNIST图像分类全流程

前段时间重新整理之前的深度学习实验笔记,翻到鞋类识别(Fashion MNIST)这个项目时,觉得特别值得拿出来单独写一篇。原因很简单:很多人入门深度学习时,都会被建议先跑通手写数字识别(MNIST&#…

2026/10/20 20:57:37 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/20 9:50:45 阅读更多 →