【从零开始学深度学习编译器】十九,MLIR的Pass机制实践

0x0. 前言

这个系列的前面几篇文章对mlir的组件有了一些粗浅的认识,这篇文章不继续讲mlir的架构。而是从实践的角度带读者来看一下,mlir帮助我做了什么,这里仍然以oneflow dialect为例。在mlir:摩尔定律终结的编译器基础结构 论文解读 这篇文章的评论部分已经简单介绍了oneflow dialect相关的组件是如何实现的。在实现了oneflow dialect的基础上,我继续来介绍一下mlir的pass机制是如何助力oneflow模型训练和推理加速的。

0x1. 背景

当前Transformer架构已经成为做AI的算法开发人员和工程师们不得不谈的基础架构。由Transformer基础架构派生出了一系列超大模型如Bert和GPT-2,在业界都有非常大的影响,并且也引领了大模型的潮流。然而大模型的高昂训练成本让很多人甚至很多公司望而却步,通常只能在预训练的大模型上做一些下游任务,因此如何加速大模型训练是十分重要的。在2019年,英伟达成功地构建并训练了最大的语言模型 GPT-2 8B,这一模型包含 83 亿参数量,是 BERT-Large 模型的 24 倍、GPT-2 的 5.6 倍。英伟达将这一模型称为「Megatron」(威震天),还开源了用来训练这一模型的 pytorch 代码:https://github.com/NVIDIA/Megatron-LM。

这篇论文中提到了很多加速大模型训练的手段,特别的如模型并行训练技术,但本人对分布式训练了解很少这里不做介绍。我这里唯一的关注点是在Megatron论文(https://arxiv.org/pdf/2104.04473.pdf)的4.2节中提到的编译优化加速模型训练:

【从零开始学深度学习编译器】十九,MLIR的Pass机制实践

Megatron 4.2节

立即进入“豆包AI人工智官网入口”;

立即学习“豆包AI人工智能在线问答入口”;

【从零开始学深度学习编译器】十九,MLIR的Pass机制实践

Megatron 4.2节

立即进入“豆包AI人工智官网入口”;

立即学习“豆包AI人工智能在线问答入口”;

这一节编译优化讲的主要是可以通过PyTorch JIT技术来做一些Op融合,比如将bias_add和gelu融合成一个算子,bias_add+dropout融合成一个算子。做了这些算子融合之后,不仅可以避免GPU重复读写数据减少显存占用,还可以减少cuda kernel launch次数对整个计算过程进行加速。

要实现论文中提到的编译优化,需要两个前置条件。一是框架提供了融合Op的实现,二是基于编译器实现一个优化Pass来自动寻找模型中可以融合的Pattern并将其重写为等价的融合Op,达到对计算图进行运行加速的目的。

0x2. BiasAdd Dropout以及融合算子简介

在OneFlow中为了对标Megatron的bias_add和dropout fuse,实现了一个fused_bias_add_mask_scale算子,做的事情就是将BiasAdd和Dropout融合成一个算子来加速。这个算子的实现过程这里不展开,重点是如何在模型中基于MLIR自动发现这种Pattern并自动将这种Pattern替换为fused_bias_add_mask_scale算子。

为了下一节更好的理解融合Pass的做法,这里对bias_add,dropout以及fused_bias_add_mask_scale这三种Op的参数列表进行简要介绍。

bias_add 算子:代码语言:javascript代码运行次数:0运行复制

>>> import oneflow as flow>>> x = flow.randn(2, 3)>>> y = flow.randn(3)>>> z = flow._C.bias_add(x, y, axis=1)

可以看到这个算子有3个参数,一个是输入Tensor,一个是bias Tensor,还有一个axis属性表示需要把bias Tensor附加到输入Tensor的哪个维度上。在Transformer结构中,带偏置的线性层(nn.Linear)就是通过一个矩阵乘法(matmul)算子和一个bias_add实现的。

nn.Dropout 算子:Dropout算子相信大家非常熟悉,不需要多解释,可以参考下方OneFlow算子文档。

【从零开始学深度学习编译器】十九,MLIR的Pass机制实践

例子:

代码语言:javascript代码运行次数:0运行复制

>>> import numpy as np>>> import oneflow as flow>>> m = flow.nn.Dropout(p=0)>>> arr = np.array(...    [...        [-0.7797, 0.2264, 0.2458, 0.4163],...        [0.4299, 0.3626, -0.4892, 0.4141],...        [-1.4115, 1.2183, -0.5503, 0.6520],...    ]... )>>> x = flow.Tensor(arr)>>> y = m(x)>>> y tensor([[-0.7797,  0.2264,  0.2458,  0.4163],        [ 0.4299,  0.3626, -0.4892,  0.4141],        [-1.4115,  1.2183, -0.5503,  0.6520]], dtype=oneflow.float32)

fused_bias_add_mask_scale:fused_bias_add_mask_scale算子需要bias_add算子的输入ab(bias),然后还需要一个由输入a调用random_mask_like Op产生的掩码Tensor mask作为它的第三个输入,最后还需要bias_add算子的axis属性和Dropout的p属性。

这里需要解释一下为什么需要mask。其实Dropout算子在实现的时候也会产生两个输出,一个是输出Tensor,一个是mask。这是因为Dropout会根据p和我们输入的随机数种子产生一个mask来决定哪些位置的神经元应该保留,哪些位置的神经元置0,为了正确的反向传播的需要我们必须保留这个mask来求取输入Tensor对应的梯度。因此在fused_bias_add_mask_scale Op中,需要将mask显示的传给这个Op,因为这个Op的输出只有一个,不会再输出一个额外的mask了。而这个mask的生成是利用oneflow内部的random_mask_like Op来生成的,这个Op接受一个输入Tensor和p以及一个随机数种子来产生一个具有一定概率分布的掩码Tensor mask。

0x3. Pattern匹配和重写

在了解了这些Op的操作数,属性以及输出之后,我们就可以基于MLIR来做针对BiasAdd和Dropout的Patten自动匹配和重写了。这个功能实现在:https://github.com/Oneflow-Inc/oneflow/pull/7709

首先,我们需要在oneflow/ir/include/OneFlow/OneFlowPatterns.td这个文件中基于MLIR的DRR框架写出自动匹配和重写的模板,实现如下:

代码语言:javascript代码运行次数:0运行复制

def GetDefaultSeed :  NativeCodeCall;def FusedBiasAddMaskScale :  NativeCodeCall;def IsAddToOutputNone: Constraint<CPred, "">;def FusedBiasAddDropoutPattern : Pattern;

NativeCodeCall是一个占位代码,我们可以通过NativeCodeCall调用我们在Dialect下手写的C++函数。比如:

代码语言:javascript代码运行次数:0运行复制

def GetDefaultSeed :  NativeCodeCall;

这里就调用了我们在OneFlow Dialect下手写的GetDefaultSeed函数,它返回一个OneFlow的DefaultAutoGenerator类生成的随机种子,这个随机种子在Pattern里面作为RandomMaskLikeOp的一个属性被使用:

代码语言:javascript代码运行次数:0运行复制

mlir::IntegerAttr GetDefaultSeed(::mlir::PatternRewriter& rewriter) {  const auto gen = CHECK_JUST(::oneflow::one::DefaultAutoGenerator());  return getSI64IntegerAttr(rewriter, (int64_t)gen->current_seed());}

类似的CreateFusedBiasAddMaskScale这个函数就是将匹配上的Pattern(BiasAddOp+DropoutOp)重写为FusedBiasAddMaskScaleOp。代码实现如下:

代码语言:javascript代码运行次数:0运行复制

::llvm::SmallVector CreateFusedBiasAddMaskScale(::mlir::PatternRewriter& rewriter,                                                                  OpResult dropout_result,                                                                  OpResult bias_add_result,                                                                  Operation* mask) {  if (auto dropout_op = llvm::dyn_cast(dropout_result.getDefiningOp())) {    if (auto bias_add_op = llvm::dyn_cast(bias_add_result.getDefiningOp())) {      SmallVector operands;      operands.push_back(bias_add_op.a());      operands.push_back(bias_add_op.b());      operands.push_back(mask->getResults()[0]);      NamedAttrList fused_bias_add_dropout_attributes = dropout_op->getAttrs();      fused_bias_add_dropout_attributes.append(llvm::StringRef("axis"), bias_add_op.axisAttr());      fused_bias_add_dropout_attributes.append(llvm::StringRef("scale"), dropout_op.rateAttr());      fused_bias_add_dropout_attributes.erase(dropout_op.rateAttrName());      auto res = rewriter                     .create(                         dropout_op->getLoc(), dropout_op->getResultTypes().front(), operands,                         fused_bias_add_dropout_attributes)                     ->getResults();      // bias_add and dropout op is expected to be erased if it is not used      return res;    }  }  return {};}

这个函数接收一个PatternRewriter对象和DropoutOp以及BiasAddOp的输出值,然后从这两个值可以取得定义它们的Op,从Op又可以取得对应的操作数和属性等。然后基于PatternRewriter对象完成创建一个新Op的过程,并在当前DropoutOp的位置完成替换,这样就完成了特定Pattern的重写工作。失效的BiasAddOp和DropoutOp由于是NoSideEffect的,在生成的IR中会自动被删掉。

接下来我们看一下IsAddToOutputNone这个约束,def IsAddToOutputNone: Constraint, "">; 这里使用CPred来自定义了一个约束,这个CPred里面可以放一个任何返回bool类型的C++函数。这里的实现为:

代码语言:javascript代码运行次数:0运行复制

bool IsAddToOutputNone(ValueRange value) { return (int)value.size() > 0 ? false : true; }

即判断Dropout Op的_add_to_output这个可选的输入是否存在,如果不存在才可以使用我们实现的这个Pass。

除了上面的常规部分之外,这里需要注意两个特殊的点,我单独列出。

NativeCodeCall的限制引发的问题

我们可以从MLIR的文档查到NativeCodeCall只能返回一个结果。所以在上面的模板匹配和重写的时候我们给重写的部分设置了2个输出,一个是FusedBiasAddMaskScaleOp的输出(目标输出),一个是使用(replaceWithValue $mask)定义的占位输出。原因是因为Dropout Op有2个输出,如果这里没有定义一个新的占位输出那么这里模板匹配重写时就会报输出个数不一样的错误。这里使用replaceWithValue的原因是它可以简单直接的完成替换一个值(mlir::Value)的功能,比较适合这里的占位作用。

RandomMaskLikeOp为什么要自定义builder

上面的实现中还有一个依赖就是需要自定义RandomMaskLikeOp的builder,新的RandomMaskLikeOp的定义如下:

代码语言:javascript代码运行次数:0运行复制

def OneFlow_RandomMaskLikeOp : OneFlow_BaseOp<"random_mask_like", [NoSideEffect, NoGrad, DeclareOpInterfaceMethods]> {  let input = (ins    OneFlow_Tensor:$like  );  let output = (outs    OneFlow_Tensor:$out  );  let attrs = (ins    DefaultValuedAttr:$rate,    DefaultValuedAttr:$seed  );  let builders = [    OpBuilder  ];  let has_check_fn = 1;  let has_logical_tensor_desc_infer_fn = 1;  let has_physical_tensor_desc_infer_fn = 1;  let has_get_sbp_fn = 1;  let has_data_type_infer_fn = 1;}

自定义builder之后需要将这个builder使用C++来实现一下:

代码语言:javascript代码运行次数:0运行复制

void RandomMaskLikeOp::build(mlir::OpBuilder& odsBuilder, mlir::OperationState& odsState,                             mlir::Value like, StringRef op_name, StringRef device_tag,                             ArrayAttr device_name, IntegerAttr scope_symbol_id,                             ArrayAttr hierarchy, mlir::FloatAttr rate, mlir::IntegerAttr seed) {  odsState.addOperands(like);  odsState.addAttribute(op_nameAttrName(odsState.name), odsBuilder.getStringAttr(op_name));  odsState.addAttribute(device_tagAttrName(odsState.name), odsBuilder.getStringAttr(device_tag));  odsState.addAttribute(device_nameAttrName(odsState.name), device_name);  if (scope_symbol_id) {    odsState.addAttribute(scope_symbol_idAttrName(odsState.name), scope_symbol_id);  }  if (hierarchy) { odsState.addAttribute(hierarchyAttrName(odsState.name), hierarchy); }  odsState.addAttribute(rateAttrName(odsState.name), rate);  odsState.addAttribute(seedAttrName(odsState.name), seed);  odsState.addTypes(like.getType());}

这样做的原因是因为,这里使用RandomMaskLikeOp生成的mask不是要替换的Dag的最外层Op,所以MLIR无法推断RandomMaskLikeOp的输出值类型(如果是单个Op的话,这个Op的输出类型就是它将要replace的那个Op的输出类型),所以我们要提供一种特殊的,不需要输出类型的builder。这个builder会做类型推断,在这个例子就是从like直接取得类型。即:odsState.addTypes(like.getType()); 这行代码。

如果不修改这个就会报类型无法匹配的错误,大概长这样:

代码语言:javascript代码运行次数:0运行复制

python3: /home/xxx/oneflow/build/oneflow/ir/llvm_monorepo-src/mlir/lib/IR/PatternMatch.cpp:328: void mlir::RewriterBase::replaceOpWithResultsOfAnotherOp(mlir::Operation*, mlir::Operation*): Assertion `op->getNumResults() == newOp->getNumResults() && "replacement op doesn't match results of original op"' failed

0x4. 测试

上面就已经讲完了所有的实现细节,我们可以构造一个OneFlow的程序来验证这个IR融合是否正常工作。测试代码如下:

代码语言:javascript代码运行次数:0运行复制

import unittestimport numpy as npimport osos.environ["ONEFLOW_MLIR_ENABLE_ROUND_TRIP"] = "1"import oneflow as flowimport oneflow.unittestdef do_bias_add_dropout_graph(test_case, with_cuda, prob):    x = flow.randn(2, 3, 4, 5)    bias = flow.randn(5)    dropout = flow.nn.Dropout(p=prob)    if with_cuda:        x = x.cuda()        bias = bias.to("cuda")        dropout.to("cuda")    eager_res = dropout(flow._C.bias_add(x, bias, axis=3))    class GraphToRun(flow.nn.Graph):        def __init__(self):            super().__init__()            self.dropout = dropout        def build(self, x, bias):            return self.dropout(flow._C.bias_add(x, bias, axis=3))    graph_to_run = GraphToRun()    lazy_res = graph_to_run(x, bias)    test_case.assertTrue(np.array_equal(eager_res.numpy(), lazy_res.numpy()))@flow.unittest.skip_unless_1n1d()class TestBiasAddDropout(oneflow.unittest.TestCase):    def test_bias_add_dropout_graph(test_case):        do_bias_add_dropout_graph(test_case, True, 1.0)if __name__ == "__main__":    unittest.main()

这里使用了nn.Graph对计算过程进行包装,即使用静态图的模式运行整个程序。nn.Graph被构建之后会生成一个Job(OneFlow的原始计算图表示),然后这个Job会被转换为MLIR表达式(OneFlow Dialect)做上面的Fuse Pass再转回Job(优化后的OneFlow计算图表示)后再做训练或者推理。

我们可以看一下使用MLIR FuseBiasAddDropout Pass前后的IR表示。首先是不使用这个Pass的MLIR表达式:

代码语言:javascript代码运行次数:0运行复制

module {  oneflow.job @GraphToRun_0(%arg0: tensor, %arg1: tensor) -> tensor {    %output = "oneflow.input"(%arg0) {data_type = 2 : i32, device_name = ["@0:0"], device_tag = "gpu", hierarchy = [1], is_dynamic = false, nd_sbp = ["B"], op_name = "_GraphToRun_0_input.0.0_2", output_lbns = ["_GraphToRun_0_input.0.0_2/out"], scope_symbol_id = 4611686018427420671 : i64, shape = [2 : si64, 3 : si64, 4 : si64, 5 : si64]} : (tensor) -> tensor    %output_0 = "oneflow.input"(%arg1) {data_type = 2 : i32, device_name = ["@0:0"], device_tag = "gpu", hierarchy = [1], is_dynamic = false, nd_sbp = ["B"], op_name = "_GraphToRun_0_input.0.1_3", output_lbns = ["_GraphToRun_0_input.0.1_3/out"], scope_symbol_id = 4611686018427420671 : i64, shape = [5 : si64]} : (tensor) -> tensor    %0 = "oneflow.bias_add"(%output, %output_0) {axis = 3 : si32, device_name = ["@0:0"], device_tag = "gpu", hierarchy = [1], op_name = "bias_add-0", output_lbns = ["bias_add-0/out_0"], scope_symbol_id = 4611686018427420671 : i64} : (tensor, tensor) -> tensor    %out, %mask = "oneflow.dropout"(%0) {device_name = ["@0:0"], device_tag = "gpu", hierarchy = [1], op_name = "dropout-dropout-1", output_lbns = ["dropout-dropout-1/out_0", "dropout-dropout-1/mask_0"], rate = 1.000000e+00 : f32, scope_symbol_id = 4611686018427428863 : i64} : (tensor) -> (tensor, tensor)    %output_1 = "oneflow.output"(%out) {data_type = 2 : i32, device_name = ["@0:0"], device_tag = "gpu", hierarchy = [1], is_dynamic = false, nd_sbp = ["B"], op_name = "_GraphToRun_0_output.0.0_2", output_lbns = ["_GraphToRun_0_output.0.0_2/out"], scope_symbol_id = 4611686018427420671 : i64, shape = [2 : si64, 3 : si64, 4 : si64, 5 : si64]} : (tensor) -> tensor    oneflow.return %output_1 : tensor  }}

然后是启动这个Pass之后获得的MLIR表达式:

代码语言:javascript代码运行次数:0运行复制

module {  oneflow.job @GraphToRun_0(%arg0: tensor, %arg1: tensor) -> tensor {    %output = "oneflow.input"(%arg0) {data_type = 2 : i32, device_name = ["@0:0"], device_tag = "gpu", hierarchy = [1], is_dynamic = false, nd_sbp = ["B"], op_name = "_GraphToRun_0_input.0.0_2", output_lbns = ["_GraphToRun_0_input.0.0_2/out"], scope_symbol_id = 4611686018427420671 : i64, shape = [2 : si64, 3 : si64, 4 : si64, 5 : si64]} : (tensor) -> tensor    %output_0 = "oneflow.input"(%arg1) {data_type = 2 : i32, device_name = ["@0:0"], device_tag = "gpu", hierarchy = [1], is_dynamic = false, nd_sbp = ["B"], op_name = "_GraphToRun_0_input.0.1_3", output_lbns = ["_GraphToRun_0_input.0.1_3/out"], scope_symbol_id = 4611686018427420671 : i64, shape = [5 : si64]} : (tensor) -> tensor    %0 = "oneflow.random_mask_like"(%output) {device_name = ["@0:0"], device_tag = "gpu", hierarchy = [1], op_name = "bias_add-0", rate = 1.000000e+00 : f32, scope_symbol_id = 4611686018427428863 : i64, seed = 4920936260932536 : si64} : (tensor) -> tensor    %1 = "oneflow.fused_bias_add_mask_scale"(%output, %output_0, %0) {axis = 3 : si32, device_name = ["@0:0"], device_tag = "gpu", hierarchy = [1], op_name = "dropout-dropout-1", output_lbns = ["dropout-dropout-1/out_0", "dropout-dropout-1/mask_0"], scale = 1.000000e+00 : f32, scope_symbol_id = 4611686018427428863 : i64} : (tensor, tensor, tensor) -> tensor    %output_1 = "oneflow.output"(%1) {data_type = 2 : i32, device_name = ["@0:0"], device_tag = "gpu", hierarchy = [1], is_dynamic = false, nd_sbp = ["B"], op_name = "_GraphToRun_0_output.0.0_2", output_lbns = ["_GraphToRun_0_output.0.0_2/out"], scope_symbol_id = 4611686018427420671 : i64, shape = [2 : si64, 3 : si64, 4 : si64, 5 : si64]} : (tensor) -> tensor    oneflow.return %output_1 : tensor  }}

可以看到上面实现的FuseBiasAddDropout Pass成功完成了BiasAdd和Dropout Op的融合。

0x5. 总结

这篇文章介绍了MLIR的Pass机制的实践,在OneFlow Dialect中已经实现了很多常用的Fuse Op并且使用MLIR来做Pattern Match和Rewrite,从而在不需要用户修改任何代码的情况下无感加速计算图以及节省显存。如果你对这部分很感兴趣,可以到我们的OneFlow仓库中查看。

0x6. 资料https://github.com/Oneflow-Inc/oneflowhttps://mlir.llvm.org/docs/DeclarativeRewrites/

以上就是【从零开始学深度学习编译器】十九,MLIR的Pass机制实践的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/11262.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月15日 17:16:30
下一篇 2025年11月15日 17:25:39

相关推荐

  • CSS mask属性无法获取图片:为什么我的图片不见了?

    CSS mask属性无法获取图片 在使用CSS mask属性时,可能会遇到无法获取指定照片的情况。这个问题通常表现为: 网络面板中没有请求图片:尽管CSS代码中指定了图片地址,但网络面板中却找不到图片的请求记录。 问题原因: 此问题的可能原因是浏览器的兼容性问题。某些较旧版本的浏览器可能不支持CSS…

    2025年12月24日
    900
  • Uniapp 中如何不拉伸不裁剪地展示图片?

    灵活展示图片:如何不拉伸不裁剪 在界面设计中,常常需要以原尺寸展示用户上传的图片。本文将介绍一种在 uniapp 框架中实现该功能的简单方法。 对于不同尺寸的图片,可以采用以下处理方式: 极端宽高比:撑满屏幕宽度或高度,再等比缩放居中。非极端宽高比:居中显示,若能撑满则撑满。 然而,如果需要不拉伸不…

    2025年12月24日
    400
  • 如何让小说网站控制台显示乱码,同时网页内容正常显示?

    如何在不影响用户界面的情况下实现控制台乱码? 当在小说网站上下载小说时,大家可能会遇到一个问题:网站上的文本在网页内正常显示,但是在控制台中却是乱码。如何实现此类操作,从而在不影响用户界面(UI)的情况下保持控制台乱码呢? 答案在于使用自定义字体。网站可以通过在服务器端配置自定义字体,并通过在客户端…

    2025年12月24日
    800
  • 如何在地图上轻松创建气泡信息框?

    地图上气泡信息框的巧妙生成 地图上气泡信息框是一种常用的交互功能,它简便易用,能够为用户提供额外信息。本文将探讨如何借助地图库的功能轻松创建这一功能。 利用地图库的原生功能 大多数地图库,如高德地图,都提供了现成的信息窗体和右键菜单功能。这些功能可以通过以下途径实现: 高德地图 JS API 参考文…

    2025年12月24日
    400
  • 如何使用 scroll-behavior 属性实现元素scrollLeft变化时的平滑动画?

    如何实现元素scrollleft变化时的平滑动画效果? 在许多网页应用中,滚动容器的水平滚动条(scrollleft)需要频繁使用。为了让滚动动作更加自然,你希望给scrollleft的变化添加动画效果。 解决方案:scroll-behavior 属性 要实现scrollleft变化时的平滑动画效果…

    2025年12月24日
    000
  • 如何为滚动元素添加平滑过渡,使滚动条滑动时更自然流畅?

    给滚动元素平滑过渡 如何在滚动条属性(scrollleft)发生改变时为元素添加平滑的过渡效果? 解决方案:scroll-behavior 属性 为滚动容器设置 scroll-behavior 属性可以实现平滑滚动。 html 代码: click the button to slide right!…

    2025年12月24日
    500
  • 为什么设置 `overflow: hidden` 会导致 `inline-block` 元素错位?

    overflow 导致 inline-block 元素错位解析 当多个 inline-block 元素并列排列时,可能会出现错位显示的问题。这通常是由于其中一个元素设置了 overflow 属性引起的。 问题现象 在不设置 overflow 属性时,元素按预期显示在同一水平线上: 不设置 overf…

    2025年12月24日 好文分享
    400
  • 网页使用本地字体:为什么 CSS 代码中明明指定了“荆南麦圆体”,页面却仍然显示“微软雅黑”?

    网页中使用本地字体 本文将解答如何将本地安装字体应用到网页中,避免使用 src 属性直接引入字体文件。 问题: 想要在网页上使用已安装的“荆南麦圆体”字体,但 css 代码中将其置于第一位的“font-family”属性,页面仍显示“微软雅黑”字体。 立即学习“前端免费学习笔记(深入)”; 答案: …

    2025年12月24日
    000
  • 如何选择元素个数不固定的指定类名子元素?

    灵活选择元素个数不固定的指定类名子元素 在网页布局中,有时需要选择特定类名的子元素,但这些元素的数量并不固定。例如,下面这段 html 代码中,activebar 和 item 元素的数量均不固定: *n *n 如果需要选择第一个 item元素,可以使用 css 选择器 :nth-child()。该…

    2025年12月24日
    200
  • 使用 SVG 如何实现自定义宽度、间距和半径的虚线边框?

    使用 svg 实现自定义虚线边框 如何实现一个具有自定义宽度、间距和半径的虚线边框是一个常见的前端开发问题。传统的解决方案通常涉及使用 border-image 引入切片图片,但是这种方法存在引入外部资源、性能低下的缺点。 为了避免上述问题,可以使用 svg(可缩放矢量图形)来创建纯代码实现。一种方…

    2025年12月24日
    100
  • 如何解决本地图片在使用 mask JS 库时出现的跨域错误?

    如何跨越localhost使用本地图片? 问题: 在本地使用mask js库时,引入本地图片会报跨域错误。 解决方案: 要解决此问题,需要使用本地服务器启动文件,以http或https协议访问图片,而不是使用file://协议。例如: python -m http.server 8000 然后,可以…

    2025年12月24日
    200
  • 如何让“元素跟随文本高度,而不是撑高父容器?

    如何让 元素跟随文本高度,而不是撑高父容器 在页面布局中,经常遇到父容器高度被子元素撑开的问题。在图例所示的案例中,父容器被较高的图片撑开,而文本的高度没有被考虑。本问答将提供纯css解决方案,让图片跟随文本高度,确保父容器的高度不会被图片影响。 解决方法 为了解决这个问题,需要将图片从文档流中脱离…

    2025年12月24日
    000
  • 为什么我的特定 DIV 在 Edge 浏览器中无法显示?

    特定 DIV 无法显示:用户代理样式表的困扰 当你在 Edge 浏览器中打开项目中的某个 div 时,却发现它无法正常显示,仔细检查样式后,发现是由用户代理样式表中的 display none 引起的。但你疑问的是,为什么会出现这样的样式表,而且只针对特定的 div? 背后的原因 用户代理样式表是由…

    2025年12月24日
    200
  • inline-block元素错位了,是为什么?

    inline-block元素错位背后的原因 inline-block元素是一种特殊类型的块级元素,它可以与其他元素行内排列。但是,在某些情况下,inline-block元素可能会出现错位显示的问题。 错位的原因 当inline-block元素设置了overflow:hidden属性时,它会影响元素的…

    2025年12月24日
    000
  • 为什么 CSS mask 属性未请求指定图片?

    解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…

    2025年12月24日
    200
  • 为什么使用 inline-block 元素时会错位?

    inline-block 元素错位成因剖析 在使用 inline-block 元素时,可能会遇到它们错位显示的问题。如代码 demo 所示,当设置了 overflow 属性时,a 标签就会错位下沉,而未设置时却不会。 问题根源: overflow:hidden 属性影响了 inline-block …

    2025年12月24日
    000
  • 如何利用 CSS 选中激活标签并影响相邻元素的样式?

    如何利用 css 选中激活标签并影响相邻元素? 为了实现激活标签影响相邻元素的样式需求,可以通过 :has 选择器来实现。以下是如何具体操作: 对于激活标签相邻后的元素,可以在 css 中使用以下代码进行设置: li:has(+li.active) { border-radius: 0 0 10px…

    2025年12月24日
    100
  • 为什么我的 CSS 元素放大效果无法正常生效?

    css 设置元素放大效果的疑问解答 原提问者在尝试给元素添加 10em 字体大小和过渡效果后,未能在进入页面时看到放大效果。探究发现,原提问者将 CSS 代码直接写在页面中,导致放大效果无法触发。 解决办法如下: 将 CSS 样式写在一个单独的文件中,并使用 标签引入该样式文件。这个操作与原提问者观…

    2025年12月24日
    000
  • 如何模拟Windows 10 设置界面中的鼠标悬浮放大效果?

    win10设置界面的鼠标移动显示周边的样式(探照灯效果)的实现方式 在windows设置界面的鼠标悬浮效果中,光标周围会显示一个放大区域。在前端开发中,可以通过多种方式实现类似的效果。 使用css 使用css的transform和box-shadow属性。通过将transform: scale(1.…

    2025年12月24日
    200
  • 为什么我的 em 和 transition 设置后元素没有放大?

    元素设置 em 和 transition 后不放大 一个 youtube 视频中展示了设置 em 和 transition 的元素在页面加载后会放大,但同样的代码在提问者电脑上没有达到预期效果。 可能原因: 问题在于 css 代码的位置。在视频中,css 被放置在单独的文件中并通过 link 标签引…

    2025年12月24日
    100

发表回复

登录后才能评论
关注微信