MLIR的TOY教程学习笔记

MLIR TOY Language

文章目录

  • MLIR TOY Language
    • 如何编译该项目
    • ch1: MLIR 前端IR解析
    • ch2: 定义方言和算子 (ODS)
      • 1. 定义方言
      • 2. 定义OP
      • 3. OP相关操作
      • 4. 定义OP ODS (Operation Definition Specification)
        • 1. 基本定义
        • 2. 添加文档
        • 3. 验证OP
        • 4. 新增构造函数
        • 5. 定义打印OP的格式
    • ch3: 高级语言特定分析和转换 (patter-rewrite) (DRR)
      • 1. c++ code实现图匹配和转化
      • 2. 声明式 DRR 实现图匹配和转化
    • ch4: 通过接口实现通用转化(pass)
      • 1. 背景
      • 2. shape推断,为代码生成做准备
          • 1. 通过C++ 代码
          • 2. 通过ODS声明interface
    • ch5:部分IR降低到低级别IR
      • 1. 方言转换
        • 1. 转换目标
        • 2. 重写模式
    • ch6: 降低到LLVM和代码生成
    • ch7: 像IR中添加复合数据类型
  • 如何学习MLIR

$(nproc)  # linux下返回CPU的核心数,可以用来编译项目

cmake报错后如何排查:

  1. make n 指令可以打印出所有执行的指令,
  2. cmake --build . --target <target> 可以分模块编译,查看是哪个模块导致的报错。

如何编译该项目

参考自: MLIR Unix-like编译

git clone https://github.com/llvm/llvm-project.git
mkdir llvm-project/build
cd llvm-project/build
# 编译
cmake -G "Unix Makefiles" ../llvm \
   -DLLVM_ENABLE_PROJECTS=mlir \
   -DLLVM_BUILD_EXAMPLES=ON \
   -DLLVM_TARGETS_TO_BUILD="Native" \
   -DCMAKE_BUILD_TYPE=Release \
   -DLLVM_ENABLE_ASSERTIONS=ON \
   -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DLLVM_ENABLE_LLD=ON \ # 加速编译
   -DLLVM_CCACHE_BUILD=ON # 缓存,加速下一次重新编译
   
# 编译
cmake --build . --target check-mlir

使用Cmake编译LLVM的官网教程

ch1: MLIR 前端IR解析

  • 官网toy-1 文档

官方将会新建一个名为toy的语言,来介绍 MLIR 的相关概念.

ch2: 定义方言和算子 (ODS)

%t_tensor = "toy.transpose"(%tensor) {inplace = true} : (tensor<2x3xf64>) -> tensor<3x2xf64> loc("example/file/path":12:1)

image-20231127220820314

LOC:source loaction for debuggging purposes

MLIR 中,每一个operation都与代码位置强相关,不像LLVM,可以随意删除。

MLIR可以自定义IR的所有属性,operation,type,同时IR总是可以简化为上述途中的格式。

这样统一的格式,就方便了MLIR解析和重新表示任何IR。

1. 定义方言

  • 定义方言
    • 代码形势
    • tablegen

2. 定义OP

通过代码操作,略

3. OP相关操作

定义一个OP后,我们就可以访问和转换它,在MLIR中,有2个主要相关的类, OperationOP

  • Opration: op的具体实现子类,具体的OP类,用于对所有数据进行操作。

  • OP: op的基类,MLIR总是值传递,MLIR一般不通过指针或者引用传递。

    void processConstantOp(mlir::Operation *operation) {
      // 将op进行类型转换。
      ConstantOp op = llvm::dyn_cast<ConstantOp>(operation);
      
    
    

4. 定义OP ODS (Operation Definition Specification)

1. 基本定义
  1. 定义OP
  2. 定义参数和结果
def ConstantOp : Toy_Op<"constant"> {
  // 文档
  let summary = "constant operation";
  let description = [{
    Constant operation turns a literal into an SSA value. The data is attached
    to the operation as an attribute. For example:

      %0 = "toy.constant"()
         { value = dense<[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]> : tensor<2x3xf64> }
        : () -> tensor<2x3xf64>
  }];
  
  // The constant operation takes an attribute as the only input.
  // `F64ElementsAttr` corresponds to a 64-bit floating-point ElementsAttr.
  // 输入
  let arguments = (ins F64ElementsAttr:$value);

  // The constant operation returns a single value of TensorType.
  // F64Tensor corresponds to a 64-bit floating-point TensorType.
  // 输出
  let results = (outs F64Tensor);
  
  // 验证器,设置为1是为了生成1个默认的验证方法,该方法会在该OP的构造器完成后调用。
  // 验证器,用于验证OP的合法性
   let hasVerifier = 1;
  
  // 构造器
  // ODS会自动生成一些简单的构造方法
  let builders = [
    // Build a constant with a given constant tensor value.
    OpBuilder<(ins "DenseElementsAttr":$value), [{
      // Call into an autogenerated `build` method.
      build(builder, result, value.getType(), value);
    }]>,

    // Build a constant with a given constant floating-point value. This builder
    // creates a declaration for `ConstantOp::build` with the given parameters.
    OpBuilder<(ins "double":$value)>
  ];
}
2. 添加文档
  let description = [{
    Constant operation turns a literal into an SSA value. The data is attached
    to the operation as an attribute. For example:

      %0 = "toy.constant"()
         { value = dense<[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]> : tensor<2x3xf64> }
        : () -> tensor<2x3xf64>
  }];
3. 验证OP
let hasVerifier = 1;
4. 新增构造函数
  // Add custom build methods for the constant operation. These methods populate
  // the `state` that MLIR uses to create operations, i.e. these are used when
  // using `builder.create<ConstantOp>(...)`.
  let builders = [
    // Build a constant with a given constant tensor value.
    OpBuilder<(ins "DenseElementsAttr":$value), [{
      // Call into an autogenerated `build` method.
      build(builder, result, value.getType(), value);
    }]>,

    // Build a constant with a given constant floating-point value. This builder
    // creates a declaration for `ConstantOp::build` with the given parameters.
    OpBuilder<(ins "double":$value)>
  ];
5. 定义打印OP的格式
  // Divert the printer and parser to `parse` and `print` methods on our operation,
  // to be implemented in the .cpp file. More details on these methods is shown below.
  let hasCustomAssemblyFormat = 1;

  // In the following format we have two directives, `attr-dict` and `type`.
  // These correspond to the attribute dictionary and the type of a given
  // variable represectively.
  let assemblyFormat = "$input attr-dict `:` type($input)";

ch3: 高级语言特定分析和转换 (patter-rewrite) (DRR)

官方toy-3

PDLL

方言之间的转换分为:

  1. 局部转换
  2. 全局转换

MLIR中使用DAG 重写器来优化转换方案。

有2种方法可以实现模式匹配转换。

  1. 命令行 c++模式匹配和重写
  2. 表驱动的声明性重写规则(DRR)

1. c++ code实现图匹配和转化

匹配IR中树状模式并替换为一组不同的操作,我们以通过实现MLIR的 Canonicalizer (规范化器) 传递 RewritePattern 来进行。

对于简单的 C++ 重写方法,涉及匹配 IR 中的树状模式并将其替换为一组不同的操作,我们可以通过实现 RewritePattern

比如对一个变量,进行二次转置其实就是变量自身,我们可以通过以下操作 transpose(transpose(X)) -> X

/// Fold transpose(transpose(x)) -> x
struct SimplifyRedundantTranspose : public mlir::OpRewritePattern<TransposeOp> {
  /// We register this pattern to match every toy.transpose in the IR.
  /// The "benefit" is used by the framework to order the patterns and process
  /// them in order of profitability.
  // 将该重写器进行注册 (针对Transpose OP)
  SimplifyRedundantTranspose(mlir::MLIRContext *context)
      : OpRewritePattern<TransposeOp>(context, /*benefit=*/1) {}

  /// This method is attempting to match a pattern and rewrite it. The rewriter
  /// argument is the orchestrator of the sequence of rewrites. It is expected
  /// to interact with it to perform any changes to the IR from here.
  mlir::LogicalResult
  matchAndRewrite(TransposeOp op,
                  mlir::PatternRewriter &rewriter) const override {
    // Look through the input of the current transpose.
    // 拿到transpose操作的变量
    mlir::Value transposeInput = op.getOperand();
    // 拿到该变量定义的地方的OP,
    TransposeOp transposeInputOp = transposeInput.getDefiningOp<TransposeOp>();

    // Input defined by another transpose? If not, no match.
    if (!transposeInputOp)
      return failure();

    // Otherwise, we have a redundant transpose. Use the rewriter.
    rewriter.replaceOp(op, {transposeInputOp.getOperand()});
    return success();
  }
};

// 为了确保该patten工作,我们需要在该OP的ODS定义声明以下字段
 hasCanonicalizer = 1 
   
// 同时我们需要注册该OP
   // Register our patterns for rewrite by the Canonicalization framework.
void TransposeOp::getCanonicalizationPatterns(
    RewritePatternSet &results, MLIRContext *context) {
  results.add<SimplifyRedundantTranspose>(context);
}


// 我们还需要开启这个pass
  mlir::PassManager pm(module->getName());
  pm.addNestedPass<mlir::toy::FuncOp>(mlir::createCanonicalizerPass());

2. 声明式 DRR 实现图匹配和转化

DRR Decalarative-rule-based pattern-match and rewrite

  • DRR的官方文档

声明性、基于规则的模式匹配和重写 (DRR) 是一种基于操作 DAG 的声明性重写器,为模式匹配和重写规则提供基于表的语法:

class Pattern<
    dag sourcePattern, list<dag> resultPatterns,
    list<dag> additionalConstraints = [],
    dag benefitsAdded = (addBenefit 0)>;

比如上面的c++代码就可以:

// Reshape(Reshape(x)) = Reshape(x)
def ReshapeReshapeOptPattern : Pat<(ReshapeOp(ReshapeOp $arg)),
                                   (ReshapeOp $arg)>;

DRR 还提供了一种方法,用于在转换以参数和结果的某些属性为条件时添加参数约束。例如,当重整形冗余时(即当输入和输出形状相同时),该转换会消除reshape。

·def TypesAreIdentical : Constraint<CPred<"$0.getType() == $1.getType()">>;
def RedundantReshapeOptPattern : Pat<
  (ReshapeOp:$res $arg), (replaceWithValue $arg),
  [(TypesAreIdentical $res, $arg)]>;

ch4: 通过接口实现通用转化(pass)

1. 背景

  • 对不同的方言.我们通常希望执行一组常见的转换和分析,

  • 为了避免每种方言实现每个转换会导致大量代码重复.

  • 设计了一种更通用的解决方案,以接口的形式,使 MLIR 基础设施与表示一样可扩展。接口为方言和操作提供了通用机制,以便为转换或分析提供信息。

2. shape推断,为代码生成做准备

1. 通过C++ 代码
// This class defines the interface for handling inlining with Toy operations.
/// We simplify inherit from the base interface class and override
/// the necessary methods.
struct ToyInlinerInterface : public DialectInlinerInterface {
  using DialectInlinerInterface::DialectInlinerInterface;

  /// This hook checks to see if the given callable operation is legal to inline
  /// into the given call. For Toy this hook can simply return true, as the Toy
  /// Call operation is always inlinable.
  bool isLegalToInline(Operation *call, Operation *callable,
                       bool wouldBeCloned) const final {
    return true;
  }

  /// This hook checks to see if the given operation is legal to inline into the
  /// given region. For Toy this hook can simply return true, as all Toy
  /// operations are inlinable.
  bool isLegalToInline(Operation *, Region *, bool,
                       IRMapping &) const final {
    return true;
  }

  /// This hook cheks if the given 'src' region can be inlined into the 'dest'
  /// region. The regions here are the bodies of the callable functions. For
  /// Toy, any function can be inlined, so we simply return true.
  bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,
                       IRMapping &valueMapping) const final {
    return true;
  }

  /// This hook is called when a terminator operation has been inlined. The only
  /// terminator that we have in the Toy dialect is the return
  /// operation(toy.return). We handle the return by replacing the values
  /// previously returned by the call operation with the operands of the
  /// return.
  void handleTerminator(Operation *op,
                        MutableArrayRef<Value> valuesToRepl) const final {
    // Only "toy.return" needs to be handled here.
    auto returnOp = cast<ReturnOp>(op);

    // Replace the values directly with the return operands.
    assert(returnOp.getNumOperands() == valuesToRepl.size());
    for (const auto &it : llvm::enumerate(returnOp.getOperands()))
      valuesToRepl[it.index()].replaceAllUsesWith(it.value());
  }
};

toy 方言上注册该接口

void ToyDialect::initialize() {
  addInterfaces<ToyInlinerInterface>();
}
2. 通过ODS声明interface
  1. 添加ODS声明
def ShapeInferenceOpInterface : OpInterface<"ShapeInference"> {
  // 接口描述
  let description = [{
    Interface to access a registered method to infer the return types for an
    operation that can be used during type inference.
  }];
  
  // 我们定义操作需要提供的接口方法。接口方法由以下部分组成:描述;字符串形式的 C++ 返回类型;字符串形式的方法名称;以及一些可选组件,
  let methods = [
    InterfaceMethod<"Infer and set the output shape for the current operation.",
                    "void", "inferShapes">
  ];
}	
  1. 将声明添加到OP中
def MulOp : Toy_Op<"mul",
    [..., DeclareOpInterfaceMethods<ShapeInferenceOpInterface>]> {
  ...
}

每个OP 都需要为 inferShapes() 方法提供定义。例如,对于乘法,结果形状被推断为输入的形状。

/// Infer the output shape of the MulOp, this is required by the shape inference
/// interface.
void MulOp::inferShapes() { getResult().setType(getLhs().getType()); }
  1. 实现pass
// 实现pass
class ShapeInferencePass
    : public mlir::PassWrapper<ShapeInferencePass, OperationPass<FuncOp>> {
  void runOnOperation() override {
    FuncOp function = getOperation();
    ...
  }
};

// 实例化pass
std::unique_ptr<mlir::Pass> mlir::toy::createShapeInferencePass() {
  return std::make_unique<ShapeInferencePass>();
}

// 注册pass
  pm.addPass(mlir::createShapeInferencePass());

ch5:部分IR降低到低级别IR

通过在同一函数中共存的多种方言来执行渐进式降低。

1. 方言转换

MLIR 有许多不同的方言,因此有一个统一的框架在它们之间进行转换非常重要。这就是 DialectConversion 框架发挥作用的地方。该框架允许将一组非法操作转换为一组合法操作。要使用这个框架,我们需要提供两件事(以及可选的第三件事):

  1. 转化目标
  2. 一组重写模式
  3. (可选)类型转化器
1. 转换目标

我们希望将计算密集型 Toy 操作转换为 AffineArithFunc 操作的组合和 MemRef 方言以进行进一步优化。为了开始降低,我们首先定义我们的转换目标:

void ToyToAffineLoweringPass::runOnOperation() {
  // The first thing to define is the conversion target. This will define the
  // final target for this lowering.
  mlir::ConversionTarget target(getContext());

  // We define the specific operations, or dialects, that are legal targets for
  // this lowering. In our case, we are lowering to a combination of the
  // `Affine`, `Arith`, `Func`, and `MemRef` dialects.
  target.addLegalDialect<affine::AffineDialect, arith::ArithDialect,
                         func::FuncDialect, memref::MemRefDialect>();

  // We also define the Toy dialect as Illegal so that the conversion will fail
  // if any of these operations are *not* converted. Given that we actually want
  // a partial lowering, we explicitly mark the Toy operations that don't want
  // to lower, `toy.print`, as *legal*. `toy.print` will still need its operands
  // to be updated though (as we convert from TensorType to MemRefType), so we
  // only treat it as `legal` if its operands are legal.
  target.addIllegalDialect<ToyDialect>();
  target.addDynamicallyLegalOp<toy::PrintOp>([](toy::PrintOp op) {
    return llvm::none_of(op->getOperandTypes(),
                         [](Type type) { return type.isa<TensorType>(); });
  });
  ...
}

上面,我们首先将玩具方言设置为非法,然后将打印操作设置为合法。我们也可以反过来做。各个操作始终优先于(更通用的)方言定义,因此顺序并不重要。详情请参阅 ConversionTarget::getOpInfo

2. 重写模式

定义了转换目标后,我们就可以定义如何将非法操作转换为合法操作。

  • DialectConversion 框架也使用RewritePatterns来执行转换逻辑。
    • 这些模式可能是之前看到的 RewritePatterns
    • 也可能是特定于转换框架 ConversionPattern 的新型模式。

ConversionPatterns 与传统的 RewritePatterns 不同,因为它们接受附加的 operands 参数,其中包含已重新映射/替换的操作数。这在处理类型转换时使用,因为模式希望对新类型的值进行操作,但与旧类型的值进行匹配。对于我们的降低,这个不变量将很有用,因为它从当前正在操作的 TensorType 转换为 MemRefType。

我们来看一段降低 toy.transpose 操作的片段:

/// Lower the `toy.transpose` operation to an affine loop nest.
struct TransposeOpLowering : public mlir::ConversionPattern {
  TransposeOpLowering(mlir::MLIRContext *ctx)
      : mlir::ConversionPattern(TransposeOp::getOperationName(), 1, ctx) {}

  /// Match and rewrite the given `toy.transpose` operation, with the given
  /// operands that have been remapped from `tensor<...>` to `memref<...>`.
  mlir::LogicalResult
  matchAndRewrite(mlir::Operation *op, ArrayRef<mlir::Value> operands,
                  mlir::ConversionPatternRewriter &rewriter) const final {
    auto loc = op->getLoc();

    // Call to a helper function that will lower the current operation to a set
    // of affine loops. We provide a functor that operates on the remapped
    // operands, as well as the loop induction variables for the inner most
    // loop body.
    lowerOpToLoops(
        op, operands, rewriter,
        [loc](mlir::PatternRewriter &rewriter,
              ArrayRef<mlir::Value> memRefOperands,
              ArrayRef<mlir::Value> loopIvs) {
          // Generate an adaptor for the remapped operands of the TransposeOp.
          // This allows for using the nice named accessors that are generated
          // by the ODS. This adaptor is automatically provided by the ODS
          // framework.
          TransposeOpAdaptor transposeAdaptor(memRefOperands);
          mlir::Value input = transposeAdaptor.input();

          // Transpose the elements by generating a load from the reverse
          // indices.
          SmallVector<mlir::Value, 2> reverseIvs(llvm::reverse(loopIvs));
          return rewriter.create<mlir::AffineLoadOp>(loc, input, reverseIvs);
        });
    return success();
  }
};

注册该pattern

void ToyToAffineLoweringPass::runOnOperation() {
  ...

  // Now that the conversion target has been defined, we just need to provide
  // the set of patterns that will lower the Toy operations.
  mlir::RewritePatternSet patterns(&getContext());
  patterns.add<..., TransposeOpLowering>(&getContext());

ch6: 降低到LLVM和代码生成

跳过~没看

ch7: 像IR中添加复合数据类型

跳过~没看

如何学习MLIR

1. 对接不同的软件框架;

2. 对接软件框架和硬件芯片。

DialectDialectConversion

image-20240721211539191

image-20240721212832417

  1. 学习MLIR基本模块;
  2. 学习MLIR提供的 Dialects ,各个 Dialects 的定位,以及为弥补软硬件 gap,提供的这些 gap 的分类和关联。

关于MLIR基本模块学习过程如下:

  1. Dialect, Attribute, Type, Operation;想象如果自己去实现,该怎么设计类;

  2. DialectConversion;想象在自己实现的前四个模块上,如何实现DialectConversion;

  3. Interface, Constraint, Trait;同样,想象自己会怎么增加这些功能;

  4. Transformation, Concalization

  5. Region, Block

    1. 基于1. 设计的Operation,
    2. 以及4. 增加的Transformation,想象如何对Operation进行抽象,提取出Region和Block的概念;
  6. Pass;

  7. 最后才是ODSDRR

ps: 这部分借鉴自知乎:

作者:i4oolish
链接:https://www.zhihu.com/question/435109274/answer/2290343429。


  • IREE 的代码结构

以 IREE 的 FLOW方言为例, 看一下IREE的代码结构.

(iree) (base) ➜  Flow git:(20240609) ✗ tree -L 1   
.
├── BUILD.bazel
├── CMakeLists.txt
├── Conversion
  	Patterns.h 和 cpp 文件, 声明各类rewirte Pattern和实现,并提供一个接口,可以注册所有pattern
├── IR
  	定义方言,OP,和interface,
├── TransformExtensions
  	没看懂
└── Transforms 声明pass,并且调用tablegen,然后将实现和声明编译为MLIR动态库.
  	Passes.td
  	实现各类pass

MLIR官网的这个教程我觉得有点抽象,整个社区的反馈也是觉得写的并不简易入门.
我比较推荐另一个博主的一篇入门博客: mlir-入门教程
该教程代码开源在GitHub, 使用的是Bazel编译工具.除此之外没有槽点.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/843599.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

简单工厂、工厂方法与抽象工厂之间的区别

简单工厂、工厂方法与抽象工厂之间的区别 1、简单工厂&#xff08;Simple Factory&#xff09;1.1 定义1.2 特点1.3 示例场景 2、工厂方法&#xff08;Factory Method&#xff09;2.1 定义2.2 特点2.3 示例场景 3、抽象工厂&#xff08;Abstract Factory&#xff09;3.1 定义3.…

视频共享融合赋能平台LntonCVS视频监控管理平台视频云解决方案

LntonCVS是基于国家标准GB28181协议开发的视频监控与云服务平台&#xff0c;支持多设备同时接入。该平台能够处理和分发多种视频流格式&#xff0c;包括RTSP、RTMP、FLV、HLS和WebRTC。主要功能包括视频直播监控、云端录像与存储、检索回放、智能告警、语音对讲和平台级联&…

buuctf web 第五到八题

[ACTF2020 新生赛]Exec 这里属实有点没想到了&#xff0c;以为要弹shell&#xff0c;结果不用 127.0.0.1;ls /PING 127.0.0.1 (127.0.0.1): 56 data bytes bin dev etc flag home lib media mnt opt proc root run sbin srv sys tmp usr var127.0.0.1;tac /f*[GXYCTF2019]Pin…

全球大模型将往何处去?

在这个信息爆炸的时代&#xff0c;我们如同站在知识的海洋边&#xff0c;渴望着能够驾驭帆船&#xff0c;探索那些深邃的奥秘。 而今天&#xff0c;我们将启航&#xff0c;透过一份精心编制的报告&#xff0c;去洞察全球大模型的未来趋势&#xff0c;探索人工智能的无限可能。…

C++初学者指南-5.标准库(第一部分)--标准库查询存在算法

C初学者指南-5.标准库(第一部分)–标准库查询存在算法 文章目录 C初学者指南-5.标准库(第一部分)--标准库查询存在算法any_of / all_of / none_ofcountcount_if相关内容 不熟悉 C 的标准库算法&#xff1f; ⇒ 简介 any_of / all_of / none_of 如果在输入范围(所有元素…

桌面小宠物发布一周,第一次以独立开发者的身份赚到了100块

收入数据(AppStore一周收入统计) AppStore付费工具榜第七 应用简介 桌面新宠(NewPet)&#xff0c;是我耗时半年开发的一款桌面宠物。我是被 QQ 宠物影响的那批人&#xff0c;上学时天天给 QQ 宠物喂食&#xff0c;很可惜它现在不在了。所以&#xff0c;我开发的初衷是想要在电…

华为HCIP Datacom H12-821 卷42

42.填空题 如图所示&#xff0c;MSTP网络中SW1为总根&#xff0c;请将以下交换机与IST域根和主桥配对。 参考答案&#xff1a;主桥1468 既是IST域根又是主桥468 既不是又不是就是25 解析&#xff1a; 主桥1468 既是IST域根又是主桥468 既不是又不是就是25 43.填空题 网络有…

通过HTML/CSS 实现各类进度条的功能。

需求&#xff1a;我们在开发中会遇到使用各式各样的进度条&#xff0c;因为当前插件里面进度条各式各样的&#xff0c;为了方便我们定制化的开发和方便修改样式&#xff0c;我们这里使用HTML和CSS样式来进行开发进度条功能。 通过本文学习我们会明白如何使用 HTML/CSS 创建各种…

【YOLOv10[基础]】热力图可视化实践② | 支持图像热力图 | 论文必备

本文将进行添加YOLOv10版本的热力图可视化功能的实践,支持图像热力图可视化。 目录 一 热力图可视化实践① 1 代码 2 效果图 二 报错处理 在论文中经常可以见到提取的物体特征以热力图的形式展示出来,将特征图以热力图的方式进行可视化在深度学习中有以下的原因: ①强调…

0711springNews新闻系统管理 实现多级评论

0611springmvc新闻系统管理-CSDN博客 0711springNews新闻系统管理项目包 实现多级评论-CSDN博客 数据库字段 需要添加父节点id&#xff0c;通过该字段实现父评论和子评论的关联关系。 对象属性 实现链表&#xff0c;通过一个父评论可以找到它对应的所有子孙评论。 业务层 实现…

像乌鸦一样思考01_玻璃杯罩住的高低蜡烛谁先灭?

大致场景 大致原理 蜡烛燃烧需要氧气&#xff0c;同时会释放二氧化碳但蜡烛燃烧特定场景下&#xff0c;释放的二氧化碳温度很高&#xff0c;密度比空气底所以就会有上传的趋势(水高温变成水蒸气也是会往上升的)持续的燃烧&#xff0c;二氧化碳不停的往上聚集&#xff0c;最终高…

全国区块链职业技能大赛国赛考题前端功能开发

任务3-1:区块链应用前端功能开发 1.请基于前端系统的开发模板,在登录组件login.js、组件管理文件components.js中添加对应的逻辑代码,实现对前端的角色选择功能,并测试功能完整性,示例页面如下: 具体要求如下: (1)有明确的提示,提示用户选择角色; (2)用户可看…

excel系列(三) - 利用 easyexcel 快速实现 excel 文件导入导出

一、介绍 在上篇文章中&#xff0c;我们介绍了 easypoi 工具实现 excel 文件的导入导出。 本篇我们继续深入介绍另一款更优秀的 excel 工具库&#xff1a;easyexcel 。 二、easyexcel easyexcel 是阿里巴巴开源的一款 excel 解析工具&#xff0c;底层逻辑也是基于 apache p…

自动化产线 搭配数据采集监控平台 创新与突破

自动化产线在现在的各行各业中应用广泛&#xff0c;已经是现在的生产趋势&#xff0c;不同的自动化生产设备充斥在各行各业中&#xff0c;自动化的设备会产生很多的数据&#xff0c;这些数据如何更科学化的管理&#xff0c;更优质的利用&#xff0c;就需要数据采集监控平台来完…

【Vue】深入了解 Vue 的 DOM 操作:从基本渲染到高级操作的全面指南

文章目录 一、Vue 中的基本 DOM 渲染1. 响应式数据2. 虚拟 DOM 二、数据绑定与指令1. v-bind2. v-model3. v-show 与 v-if4. v-for 三、与 DOM 相关的生命周期钩子1. mounted 钩子2. updated 钩子 四、动态样式与类1. 动态样式2. 动态类 五、Vue 3 中的新的 DOM 操作 API1. ref…

某4G区域终端有时驻留弱信号小区分析

这些区域其实是长时间处于连接态的电信卡4G终端更容易出现。 出现问题时都是band1 100频点下发了针对弱信号的1650频点的连接态A4测量事件配置&#xff08;其阈值为-106&#xff09;。而这个条件很容易满足&#xff0c;一旦下发就会切到band3 1650频点。 而1650频点虽然下发ban…

文献解读-液体活检-第二十期|《连续循环肿瘤DNA检测可预测肝癌患者早期复发:一项前瞻性研究》

关键词&#xff1a;液体活检&#xff1b;基因测序&#xff1b;变异检测&#xff1b; 文献简介 标题&#xff08;英文&#xff09;&#xff1a;Serial circulating tumor DNA to predict early recurrence in patients with hepatocellular carcinoma: a prospective study标题…

uniapp判断h5/微信小程序/app端+实战展示

文章目录 导文使用条件编译的基本语法常见的平台标识符示例实战展示使用场景举例注意事项 导文 这里是导文 当你在开发Uni-app时&#xff0c;需要根据不同的平台&#xff08;比如App端、H5端、微信小程序等&#xff09;来执行不同的代码逻辑&#xff0c;可以使用条件编译来实现…

[PM]数据分析

概览 数据的定义 运营数据 分析的目的 数据分析流程 1.明确目标 2.数据来源 3.采集数据 4.数据整理 5.数据分析 趋势分析 当数据出现异常&#xff0c;一般从3个角度去查找问题&#xff1a; 1.技术层面&#xff0c;是不统计出错&#xff0c;或者产品出现bug 工 2.产品层面&am…

SCI一区级 | Matlab实现SSA-CNN-GRU-Multihead-Attention多变量时间序列预测

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.【SCI一区级】Matlab实现SSA-CNN-GRU-Multihead-Attention麻雀算法优化卷积门控循环单元融合多头注意力机制多变量时间序列预测&#xff0c;要求Matlab2023版以上&#xff1b; 2.输入多个特征&#xff0c;输出单个…