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 attachedto 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 attachedto 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::LogicalResultmatchAndRewrite(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);
}// 我们还需要开启这个passmlir::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 anoperation 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>();
}// 注册passpm.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::LogicalResultmatchAndRewrite(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
├── ConversionPatterns.h 和 cpp 文件, 声明各类rewirte Pattern和实现,并提供一个接口,可以注册所有pattern
├── IR定义方言,OP,和interface,
├── TransformExtensions没看懂
└── Transforms 声明pass,并且调用tablegen,然后将实现和声明编译为MLIR动态库.Passes.td实现各类pass

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

降雨量预测 | Matlab基于ARIMA-RBF降雨量预测

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 降雨量预测 | Matlab基于ARIMA-RBF降雨量预测 注&#xff1a;程序和数据放在一个文件夹。 程序语言为matlab&#xff0c;程序可出预测效果图&#xff0c;指标图; 代码特点&#xff1a;参数化编程、参数可方便更改、代…

浅聊 Three.js 屏幕空间反射SSR-SSRShader

浅聊 Three.js 屏幕空间反射SSR(2)-SSRShader 前置基础 渲染管线中的相机和屏幕示意图 -Z (相机朝向的方向)||| -------------- <- 屏幕/投影平面| | || | || | (f) | <- 焦距| | ||…

前端vue框架的项目文件创建及常见Vue指令运用

前言 本文介绍前端Vue框架&#xff0c;先从npm工具创建的Vue项目开始&#xff0c;对项目结构的一些文件用途进行说明&#xff0c;随后对Vue文件编写所用的两种风格&#xff08;选项式API和组合式API风格&#xff09;做了区分&#xff0c;同时对编写代码中常见的生命周期钩子函…

Pytorch使用前期准备

一、检查英伟达驱动和CUDA Toolkit是否正确安装 1.任务管理器性能选项卡中能正确显示显卡型号则表示显卡驱动正确安装 2. CUDA Toolkit会跟随pytorch自动安装 二、虚拟环境的准备 Miniconda — Anaconda documentationhttps://docs.anaconda.com/miniconda/ 1.安装anaconda或者…

腾讯元宝上线“3D角色梦工厂”:快速生成专属3D角色!

7月16日&#xff0c;腾讯旗下大模型应用“腾讯元宝”上线“3D角色梦工厂”&#xff0c;允许用户通过上传一张五官清晰的正面头像&#xff0c;并选择不同的角色模板&#xff0c;迅速生成个人3D角色&#xff01; 技术特点 “3D角色梦工厂”将大模型生成技术与3D应用相结合&#…

大模型(LLM)选择指南:AI解决方案的12个决策点

今天我们来看看国外各家领先的大型语言模型&#xff08;LLM&#xff09;&#xff0c;这些模型来自OpenAI、Google、Anthropic、Cohere、Meta、Mistral AI以及Databricks等不同的供应商。我们会根据几个关键因素来评估这些模型&#xff0c;包括性能&#xff08;涵盖价格、质量和…

NET 语言识别,语音控制操作、语音播报

System.Speech. 》》System.Speech.Synthesis; 语音播报 》》System.Speech.Recognition 语音识别 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Speech.Recog…

在 Windows 上运行 Linux:WSL2 完整指南(二)

系列文章目录 在 Windows 上运行 Linux&#xff1a;WSL2 完整指南&#xff08;一&#xff09;&#x1f6aa; 在 Windows 上运行 Linux&#xff1a;WSL2 完整指南&#xff08;二&#xff09;&#x1f6aa; 文章目录 系列文章目录前言四、常见问题及解决方法问题二&#xff1a;0…

昇思25天学习打卡营第17天|LLM-基于MindSpore的GPT2文本摘要

打卡 目录 打卡 环境准备 准备阶段 数据加载与预处理 BertTokenizer 部分输出 模型构建 gpt2模型结构输出 训练流程 部分输出 部分输出2&#xff08;减少训练数据&#xff09; 推理流程 环境准备 pip install -i https://pypi.mirrors.ustc.edu.cn/simple mindspo…

AV1技术学习:Affine Motion Compensation

一、Affine Model Parameter 除了传统的平移运动补偿&#xff0c;AV1 还支持仿射变换模型&#xff0c;将当前像素点 (x, y) 通过以下方式投影到参考帧中的预测像素点 (x, y). 参数 (h13, h23) 对应于平移模型中使用的常规运动向量。 参数 h11 和 h22 控制垂直和水平轴上的比例…

Spring后端框架复习总结

之前写的博客太杂,最近想把后端框架的知识点再系统的过一遍,主要是Spring Boot和Mybatis相关,带着自己的理解使用简短的话把一些问题总结一下,尤其是开发中和面试中的高频问题,基础知识点可以参考之前写java后端专栏,这篇不再赘述。 目录 Spring什么是AOP?底层原理?事务…

【HarmonyOS NEXT】网络请求 - 分页加载

分页加载关键字&#xff1a;onReachEnd 一、申请网络权限 在 module.json5 文件中&#xff0c;添加网络权限&#xff1a; {"module": {..."requestPermissions": [{"name": "ohos.permission.INTERNET","usedScene": {&qu…

K8S实战进阶

title ‘K8S实战进阶’ date 2024-04-02T16:57:3608:00 draft true 一、搭建Kubernetes集群 1.1 搭建方案 1.1.1 minikube minikube 是一个工具&#xff0c; 能让你在本地运行 Kubernetes。 minikube 在你的个人计算机&#xff08;包括 Windows、macOS 和 Linux PC&…

图像生成(Text-to-Image)发展脉络

这篇博客对 图像生成&#xff08;image generation&#xff09; 领域的经典工作发展进行了梳理&#xff0c;包括重要的一些改进&#xff0c;目的是帮助读者对此领域有一个整体的发展方向把握&#xff0c;并非是对每个工作的详细介绍。 脉络发展&#xff08;时间顺序&#xff0…

WGS84经纬度坐标 GCJ02火星坐标 BD09百度坐标互相转换

WGS84经纬度坐标 GCJ02火星坐标 BD09百度坐标互相转换 背景&#xff1a;uniapp做的微信小程序&#xff0c;使用到了相机拍照并获取位置坐标信息&#xff1b;在腾讯地图上展示坐标点位置信息&#xff1b; 由于业务需要我们的PC端用的不是腾讯地图&#xff0c;需要使用WGS84坐标或…

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

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

03 Git的基本使用

第3章&#xff1a;Git的基本使用 一、创建版本仓库 一&#xff09;TortoiseGit ​ 选择项目地址&#xff0c;右键&#xff0c;创建版本库 ​ 初始化git init版本库 ​ 查看是否生成.git文件&#xff08;隐藏文件&#xff09; 二&#xff09;Git ​ 选择项目地址&#xff0c…

Redis分布式系统中的主从复制

本篇文章主要对Redis的主从复制进行讲解。主要分析复制的原理&#xff0c;包括:建立复制、全量复制、部分复制、全量复制、心跳检测等。希望本篇文章会对你有所帮助。 文章目录 一、主从复制简介 二、配置主从复制模式 断开主从复制 安全性 只读 传输延迟 三、拓扑结构 四、主…

Java开发之Java容器

#来自ゾフィー&#xff08;佐菲&#xff09; 1 总览 1.1 List ArrayList&#xff1a; Object[]数组Vector&#xff1a;Object[]数组LinkedList&#xff1a; 双向链表&#xff0c;JDK1.6 之前为循环链表&#xff0c;JDK1.7 取消了循环 1.2 Set HashSet&#xff1a;无序&#xf…

mybatis 报CannotGetJdbcConnectionException

目录 报错起因 报错截图 运行环境 数据库配置 解决思路 报错起因 在web项目上拉取代码启动web服务抛CannotGetJdbcConnectionException。 报错截图 运行环境 windows idea maven tomcat springMVC mybatis 数据库配置 urlxxx driverClassNamexxx usernamexxx pass…