OneFlow源码解析:Op、Kernel与解释器

Op与Kernel的注册

继续追踪执行流程会发现,ReluFunctor在构造UserOpExpr时会用到UserOpRegistryMgr管理的Op与Kernel。Op表示算子的描述信息,Kernel在不同设备上实现计算。

注册信息保存在私有的map变量中。UserOpRegistryMgr的头文件

hhttps://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/framework/user_op_registry_manager.h)中定义了3个宏,REGISTER_USER_OP、REGISTER_USER_OP_GRAD、REGISTER_USER_KERNEL分别用于注册op、grad_op、kernel。

1.1 ReluOp的注册

REGISTER_USER_OP负责UserOp的注册。通过检索代码可以找到这个宏的使用场景。ReluOp相关的源代码在这3个文件中:

  • class定义:
  • build/oneflow/core/framework/op_generated.h
  • 注册op、op的部分实现:
  • build/oneflow/core/framework/op_generated.cpp
  • 主要实现:
  • oneflow/oneflow/user/ops/relu_op.cpp

REGISTER_USER_OP宏在op_generated.cpp中展开后代码如下:

static UserOpRegisterTrigger<OpRegistry> g_register_trigger715 =  ::oneflow::user_op::UserOpRegistryMgr::Get()  .CheckAndGetOpRegistry("relu")  .Input("x")  .Output("y")  .SetGetSbpFn(&ReluOp::GetSbp)  .SetLogicalTensorDescInferFn(&ReluOp::InferLogicalTensorDesc)  .SetPhysicalTensorDescInferFn(&ReluOp::InferPhysicalTensorDesc)  .SetDataTypeInferFn(&ReluOp::InferDataType);

调用流程如下:

OneFlow源码解析:Op、Kernel与解释器

CheckAndGetOpRegistry

https://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/framework/user_op_registry_manager.cpp#L33)会创建一个OpRegistry(https://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/framework/user_op_registry.h#L91)对象,这个类和UserOpRegisterTrigger(https://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/framework/user_op_registry_manager.h#L63)类一样,只是为构造OpRegistryResult(https://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/framework/user_op_registry.h#L62)用的中间类型。

OpRegistry会暂存中间结果并在Finish中设置一些默认推导逻辑。UserOpRegisterTrigger的构造函数会调用注册逻辑。静态变量就是为了触发构造函数从而调用注册逻辑,将构造好的OpRegistryResult保存到UserOpRegistryMgr(https://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/framework/user_op_registry_manager.h#L29)(key是op_type,如relu)。

ReluOp表示一个具体的op_type,负责为OpRegistryResult提供Op特有的方法。

OpRegistryResult把不同的Op抽象为一个通用的结构(便于统一注册管理),主要包含描述信息,保存了op的输入输出描述,以及数据类型、sbp等的推导逻辑函数。对于relu来说,主要是记录了几个推导函数要调用ReluOp的静态方法;op_def主要包含input/output的名字。

1.2 ReluKernel的注册

ReluKernel在relu_kernel.cpp中注册,过程和Op的注册类似。REGISTER_USER_KERNEL宏产开后如下所示:

static UserOpRegisterTrigger<OpKernelRegistry> g_register_trigger0 =  UserOpRegistryMgr::Get().    CheckAndGetOpKernelRegistry("relu").    .SetCreateFn(...)    .SetIsMatchedHob(UnaryPrimitiveExists(ep::primitive::UnaryOp::kRelu, "y", "x"))    .SetInplaceProposalFn([](const user_op::InferContext&,                             const user_op::AddInplaceArgPair& AddInplaceArgPairFn) -> Maybe<void> {      OF_RETURN_IF_ERROR(AddInplaceArgPairFn("y", 0, "x", 0, true));      return Maybe<void>::Ok();    });

注意SetCreateFn只是把一个如下的lambda表达式赋值给result_.create_fn,这个字段很重要,后续执行就是通过它获取kernel。

[]() {    return user_op::NewOpKernel<UnaryPrimitiveKernel>(        "y", "x", [](user_op::KernelComputeContext* ctx) {            const user_op::TensorDesc* src = ctx->TensorDesc4ArgNameAndIndex("x", 0);            const user_op::TensorDesc* dst = ctx->TensorDesc4ArgNameAndIndex("y", 0);            return ep::primitive::NewPrimitive<ep::primitive::ElementwiseUnaryFactory>(                ctx->device_type(), ep::primitive::UnaryOp::kRelu, src->data_type(),                dst->data_type());        });}

对于relu来说,NewOpKernel就是new一个UnaryPrimitiveKernel对象并返回函数指针。

最终注册的结果,会把OpKernelRegistryResult保存到UserOpRegistryMgr(key是op_type_name,如”relu”)。

1.3 Op和Kernel注册相关的类关系图

OneFlow源码解析:Op、Kernel与解释器

2

UserOpExpr的构造

上一篇提到,functional_api.yaml.cpp中的functional::Relu函数通过find(“Relu”)获取预先注册的PackedFunctor<impl::ReluFunctor>,调用其call方法会执行impl::ReluFunctor。

ReluFunctor

https://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/functional/impl/activation_functor.cpp#L38)的核心代码如下:

class ReluFunctor { public:  ReluFunctor() { op_ = CHECK_JUST(one::OpBuilder("relu").Input("x", 1).Output("y", 1).Build()); }  Maybe<Tensor> operator()(const std::shared_ptr<Tensor>& x, bool inplace) const {    // 忽略inplace相关逻辑    return OpInterpUtil::Dispatch<Tensor>(*op_, {x});  } private:  std::shared_ptr<OpExpr> op_;};

ReluFunctor

https://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/functional/impl/activation_functor.cpp#L40)的构造函数中,主要是构造UserOpExpr(https://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/framework/op_expr.h#L131)。

每一个user op 通过OpBuilder的Build()后,都会生成相应的UserOpExpr,用于存储属性、类型/shape/设备等推导方法,用于接下来op/kernel的实际计算。UserOpExpr包含以下成员:

  • base_attrs_
  • tensor_desc_infer_fn_
  • dtype_infer_fn_
  • device_and_stream_infer_fn_

它们分别用于存储该user op相关attrs属性、input/output tensor shape推导方法、数据类型data type推导方法、设备及计算流推导方法等。除了常用的UserOpExpr、还有一些用于系统op的BuiltinOpExpr。

OpBuilder的Input/Output调用主要是操作UserOpConf的proto对象,Build函数内会修改UserOpConf对象,比如根据OpRegistryResult::op_def补充默认值到attr。

之后构造UserOpExpr对象,UserOpConf对象被保存到UserOpExpr的父类BuiltinOpExprImpl<UserOpConf>的op_proto_字段,对于relu来说,op_proto_主要保存input, output等信息。UserOpExpr初始化时会从OpRegistryResult拷贝函数变量。

3

Functor的执行

ReluFunctor执行的核心逻辑是调用OpInterpUtil::Dispatch。调运顺序如下:

OneFlow源码解析:Op、Kernel与解释器

整个链路很长,本篇笔记只以Eager Local Mode下,对主要执行流程做一些说明。

3.1 根据环境和输入选择解释器

Dispatch调用的GetInterpreter(https://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/framework/op_interpreter/op_interpreter_util.cpp#L147)返回的是一个AutogradInterpreter(https://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/framework/op_interpreter.h#L168)对象,这个类是在其内含的OpExprInterpreter成员变量基础之上增加了autograd的功能。GetInterpreter内实际构造的是以下3种Interpreter,在Build函数返回时转为AutogradInterpreter。

  • LazyInterpreter: 用于lazy mode下的分布式静态图执行模式
  • EagerLocalInterpreter: 用于eager local mode本地单卡执行模式(和pytorch单卡或DDP对齐)
  • EagerGlobalInterpreter: 用于eager global mode,的分布式动态图执行模式

各个Interpreter的关系如下:

OneFlow源码解析:Op、Kernel与解释器

GetInterpreter的作用是根据输入和环境等信息,选择一个合适的解释器。

接着在Dispatch中调用解释器的
AutogradInterpreter::Apply方法,在这个方法内调用internal_->Apply(…)(
https://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/framework/op_interpreter/op_interpreter.cpp#L111
),也就是上述3个解释器的Apply方法。

3.2 Apply

通过上面我们知道,EagerLocalInterpreterEagerGlobalnterpreterLazyInterpreter 都将为其包裹上AutogradInterpreter的壳,通过AutogradInterpreter触发Apply的调用。顾名思义,AutogradInterpreter的作用主要是和autograd相关,其主要为eager mode下前向的op节点插入对应的,用于反向计算grad的节点。

下面以最常用的(Eager Mode)模式,讲解Apply的执行方法。在Eager Mode(无论是eager local还是eager consistent)模式下,实际都会走到EagerInterpreter的Apply(https://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/framework/op_interpreter/op_interpreter.cpp#L51)方法:

Maybe<void> EagerInterpreter::Apply(const OpExpr& op_expr, const TensorTuple& inputs,                                    TensorTuple* outputs, const OpExprInterpContext& ctx) const {#define APPLY_IF(op_type)                                                if (const auto* op = dynamic_cast<const op_type##Expr*>(&op_expr)) {     return ApplyImpl(*op, inputs, outputs, ctx);                         }  APPLY_IF(UserOp);  APPLY_IF(VariableOp);  APPLY_IF(CastToLocalOp);  APPLY_IF(CastFromLocalOp);  APPLY_IF(GlobalToGlobalOp);  APPLY_IF(CastToGlobalOp);  APPLY_IF(CastFromGlobalOp);  APPLY_IF(DistributeSplitOp);  APPLY_IF(DistributeCloneOp);  APPLY_IF(DistributeConcatOp);  APPLY_IF(DistributeAddOp);  APPLY_IF(FunctionOp);  APPLY_IF(SelectTopNOp)#undef APPLY_IF  OF_UNIMPLEMENTED() << "The type " << op_expr.op_type_name()                     << " has not been supported in EagerInterpreter::Apply.";}

这里通过宏定义APPLY_IF,增加了对不同类型op的分支处理,将op_expr dynamic_cast成相应子类op实现的Expr,如对于大多数用户来说,用到的op都是UserOp类型,所以这里实际上会走到这个分支中:

if (const auto* op = dynamic_cast<const UserOpExpr*>(&op_expr)) {    return ApplyImpl(*op, inputs, outputs, ctx);}

再看看
EagerLocalInterpreter::ApplyImpl(
https://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/framework/op_interpreter/eager_local_op_interpreter.cpp#L209
):

Maybe<void> EagerLocalInterpreter::ApplyImpl(const UserOpExpr& op_expr, const TensorTuple& inputs,                                             TensorTuple* outputs,                                             const OpExprInterpContext& ctx) const {  return NaiveInterpret(op_expr, inputs, outputs, ctx);}

其最终实现是NaiveInterpret(https://github.com/Oneflow-Inc/oneflow/blob/v0.8.1/oneflow/core/framework/op_interpreter/eager_local_op_interpreter.cpp#L88

3.3 NaiveInterpret

NaiveInterpret简单来说,主要用于做以下四件事:

  • check input tensor的device是否一致
  • 生成output tensor
  • 为output tensor推导和检查shape/stride/dtype
  • 构建op执行指令,并派发至vm

简化版的代码如下:

Maybe<void> NaiveInterpret(const UserOpExpr& user_op_expr, const TensorTuple& inputs,                           const Symbol<Device>& default_device, TensorTuple* outputs,                           const OpExprInterpContext& ctx) {  const auto& attrs = ctx.attrs;  // 检查input tensor是否位于相同device上  ...        // 推导outout tensor的设备类型  // Infer devices  if (!user_op_expr.has_device_and_stream_infer_fn()) {    stream = JUST(GetDefaultStreamByDevice(default_device));    for (int i = 0; i < outputs->size(); i++) {      auto* tensor_impl = JUST(TensorImpl4Tensor(outputs->at(i)));      *JUST(tensor_impl->mut_device()) = default_device;    }  } else {    need_check_mem_case = false;    stream = JUST(user_op_expr.InferDeviceAndStream(attrs, inputs, outputs));  }  // 推导outout tensor的形状、数据类型  // Infer shapes and dtypes  const auto& device_tag = stream->device()->type();  JUST(user_op_expr.InferPhysicalTensorDesc(      attrs, device_tag,      [&](int32_t i) -> const TensorMeta* {        return CHECK_JUST(TensorImpl4Tensor(inputs[i]))->mut_tensor_meta();      },      [&](int32_t i) -> TensorMeta* {        // using thread_local TensorMeta pointer if inplace.        // using tensor_impl TensorMeta pointer if not inplace.        return output_tensor_metas->at(i);      }));  // 为output tensor初始化eager_blob_object  for (int i = 0; i < output_eager_blob_objects->size(); i++) {    auto* tensor_impl = JUST(TensorImpl4Tensor(outputs->at(i)));    if (!output_eager_blob_objects->at(i)) {      if (!JUST(user_op_expr.SupportNonContiguous())) {        std::shared_ptr<Stride> stride(new Stride(*tensor_impl->shape()));        tensor_impl->mut_tensor_meta()->set_stride(stride);      }      const auto& dep_object = NewLocalDepObject();      JUST(tensor_impl->InitEagerBlobObject(dep_object));      output_eager_blob_objects->at(i) = JUST(tensor_impl->eager_blob_object());    } else {      // output i is inplaced.      // check thread_local TensorMeta and tensor_impl TensorMeta.      CHECK_OR_RETURN(tensor_impl->tensor_meta()->shape() == output_tensor_metas->at(i)->shape());      CHECK_OR_RETURN(tensor_impl->tensor_meta()->dtype() == output_tensor_metas->at(i)->dtype());    }  }  // 从user_op_expr中取出kernel  const auto& kernel = JUST(user_op_expr.MutKernel4Stream(stream));  kernel->set_need_check_mem_case(need_check_mem_case);  for (int64_t index : kernel->output_tuple_indexes4mut2_obns()) {    output_eager_blob_objects->at(index)->set_is_shape_synced(false);  }  // kernel dispatch至VM,等待后续实际的调度执行  JUST(PhysicalRun([&](InstructionsBuilder* builder) -> Maybe<void> {    return builder->Call(kernel, input_eager_blob_objects, output_eager_blob_objects, ctx, stream);  }));  return Maybe<void>::Ok();}

PhysicalRun接受一个lambda functor作为参数,这里即InstructionsBuilder->Call方法,该方法接受kernel、input/output的eager blob object、kernel执行的上下文作为参数。Call方法实际会完成OpCall指令的构建,并最终将其派发至vm指令列表中,等待VM实际调度执行。

本文链接:https://www.dzdvip.com/36742.html 版权声明:本文内容均来源于互联网。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 395045033@qq.com,一经查实,本站将立刻删除。
(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022年8月2日 23:14
下一篇 2022年8月3日 11:10

相关推荐

  • 嘉峪关海拔多少米(天下第一关嘉峪关介绍)

    “嘉谷关海拔多少米?1.嘉谷关海拔是1412-2722米之间,被誉为“天下第一雄关”的嘉峪关,位于甘肃省嘉峪关市以西5公里最窄的山谷中间。2.城市两侧的城墙穿过沙漠戈壁,北接黑山长城,南接第一码头。3.嘉谷关是明朝长城的最西端,历史上曾被称为河西咽喉。” “嘉峪关,号称“天下第一雄关”,位于甘肃省嘉峪关市西5千米处最狭窄的山谷中部,城关两侧的城墙横穿沙漠戈壁,北连黑山悬壁长城,南接天下第一墩,是明长城最西端的关口,历史上曾被称为河西咽喉,因地势险要,建筑雄伟,有连陲锁钥之称。嘉峪关是古代“丝绸之路”的交通要塞,中国长城三大奇观之一(东有山海关、中有镇北台、西有嘉峪关)。嘉峪关始建于明洪武五年(1372年),由内城、外城、罗城、瓮城、城壕和南北两翼长城组成,全长约60千米。 天下第一关嘉峪关介绍 汽车驶过荒漠,眼前的一切变得苍凉与寂静,虽然是8月盛夏,但嘉峪关市的地貌依旧贫瘠,连绿树都成了稀有植物。这是独属于西北的气质,原始粗犷却又饱含生命力,令人变得渺小,变得敬畏。 这次旅途的目的地,是建于荒漠中的嘉峪关。儿时读书时,便对这座关隘充满向往,我最为敬重的两位历史名人:林则徐与左宗棠,都因各自因缘来过这里。 林公目睹雄关的高耸巍峨,写下了:“严关百尺界天西,万里征人驻马蹄;飞阁遥连秦树直,缭垣斜压陇云低”的诗句。左宗棠收复新疆回来,意得志满,站在嘉峪关城楼上看江河豪迈,落日高悬,写下了“天下第一雄关”的牌匾。 嘉峪关是值得这样称赞的,这座600多年的险要雄关,是西北最坚韧的堡垒。 嘉峪关的地理选址很巧妙,南部是险峻的讨赖河与高不可攀的祁连山,北部是黑山和无边无际的荒漠,只有中间这条孤道可走,扼住了这条道便扼住了整条丝绸之路。 嘉峪关始建于明洪武五年(1372年),由明朝开国大将冯胜所建,最初是土城,之后的160多年来陆续筑造城墙,形成了内外两城的格局。 虽说明朝才开始建,但宋元时期有关无城,只是充当商贸节点。明朝建国后,朱元璋已无深入西域的野心,便在此兴起关隘,作为边界横隔了东西两方。《明会典》中记载:“嘉峪关外,并称西域”,在当时出了这座关隘,就算来到了国外。 嘉峪关比想象中的还要雄伟,内城高10.7米,外城高12米,通体以黄土夯筑,以砖包墙,雄伟坚固,威严不可侵犯。游客走入关内,如同陷入城堡之中,显得渺小脆弱,黄土蓝天的压迫感弥漫而来,令人充满敬畏之心。 这些天堑般…

    2022年8月9日
    39
  • 用好这5步,打造自传播型品牌

    做营销往往会围绕产品去策划一系列活动,但经常出力费脑却没讨好用户。自传播是基于一个事件、一个产品或者营销活动自身的吸引力,激发人们自愿转发分。自传播这个概念大家应该并不陌生,但是真正把自传播整个体系做过完整研究的人还是很少的。今天,本文作者分享了五个打造自传播型品牌的步骤。 信息冗余时代,一个单品或者品牌如何能做到自传播,无需过多触发用户能自主选择,甚至分享呢?之前有写“社交货币”应用,分享常见的社交货币心理。有小伙伴问,具体怎么应用到自己产品和品牌上呢? 先看一个词,NPS推荐值。NPS净推荐值:NPS即净推荐值(Net Promoter Score),是一种计量客户将会向其他人推荐企业或服务可能性的指数,是目前最流行的顾客忠诚度分析指标。 正如我们常说,“别人说好才是真的好”,王婆卖瓜、强推功能性产品,暴力刷屏的信息,早已经在消费者脑海里产生了抗体,自动免疫和过滤。茶余饭后,用户能自发提起产品才是品牌的护城河。 比如:新店开业,黄牛排队300元一杯的深圳茶颜悦色;出差武汉,同事朋友都点名带正宗的周黑鸭;三俩好友逛商场,面对众多餐饮店,难以抉择时去了只接待4人内的太二酸菜鱼。 茶颜悦色,你可以说炒作,无知的用户都是乌合之众; 周黑鸭,你可以说地域的特色属性,让人有所心生向往; 太二,你可以说极致的人群定位,降低用户选择成本。 但是,他们都有一个共同点,自带传播属性,社交货币型产品。那么,究竟怎样设计一款自带传播属性的社交货币型产品呢?和你分享5点思路也许能让你的产品和品牌更好的自传播。 01 创始人自带故事 如何讲好一个故事?可以参考《金字塔原理》SCQA架构的延伸,从定位到改变。定位 背景 矛盾 行动 改变=创始人故事,来自“领秀社”的观点。 作为爱情象征物的的德芙巧克力(DOVE),其创始人故事就是一个凄美动人的爱情故事;作为个人传奇经历代表的褚橙,其创始人故事就是一个励志的情怀故事。小罐茶创始人杜国楹的“寻茶故事”,就很符合从定位到改变的变迁。 第1步,核心信息定位:“小罐茶”让茶易于接近和传承; 第2步,故事背景:创始人杜国楹踏上“全国寻茶”之路; 第3步,矛盾出现:茶叶没有行业标准,通常只用产地来区分;对消费者来说,觉得入行门槛高,买卖不够透明,缺乏安全感;品牌集中度极低,茶企通常难以发展; 第4步,解决策略:尝试把“有机”做成一个好茶的标准,但是发现有…

    2021年6月2日
    48
  • 抖音怎么删除自己的作品?

    图文教程 抖音短视频中发布上传了作品之后,如果我们想要删除掉一些已经发布的作品,要怎么删除呢? 1、打开抖音,点击我 2、作品,找到要删除的视频进入 3、点击“三个点”删除,弹出窗口,点击确认就可以删除了 总结 1、打开抖音进入个人中心 2、在作品中点击要删除的作品 3、进入“三个点”删除

    2021年12月20日
    24
  • 老舍的话剧有哪些(老舍的话剧代表作)

    “老舍的主要话剧作品有《残雾》《张自忠》《面子问题》《大地龙蛇》《归去来今》《方珍珠》《龙须沟》《生日》《春华秋实》《青年突击队》《西望长安》《茶馆》《红大院》《女店员》《全家福》《宝船》《神拳》(又名《义和团》)《荷珠配》《火车上的威风》《国家至上》(与宋之的合著)《王老虎》《桃李春风》。” 1949年,老舍从美国归国,受到各方热烈欢迎。为了有安静的创作环境,他向周恩来总理提出可否私人购买一所小房,周总理爽快地答应了。朋友替他物色到东城丰盛胡同10号(后地名变更为丰富胡同19号)的这座小院,他花了100匹白布买下来,次年全家搬了进去。老舍一生爱自然、爱生活、爱干净,十分用心布置小院,院中草木葱茏、花香四溢,屋内纤尘不染、书画高致,来做客的朋友都赞不绝口。 老舍话剧代表作品 1、《茶馆》 《茶馆》是现代文学家老舍于1956年创作的话剧,1957年7月初载于巴金任编辑的《收获》杂志创刊号。1958年6月由中国戏剧出版社出版单行本。 剧作展示了戊戌变法、军阀混战和新中国成立前夕三个时代近半个世纪的社会风云变化。通过一个叫裕泰的茶馆揭示了近半个世纪中国社会的黑暗腐败、光怪陆离,以及在这个社会中的芸芸众生。 剧本中出场的人物近50人,除茶馆老板之外,有吃皇粮的旗人、办实业的资本家、清官里的太监、信奉洋教的教士、穷困潦倒的农民,以及特务、打手、警察、流氓、相士等。 人物众多但性格鲜明,能够“闻其声知其人”,“三言两语就勾出一个人物形象的轮廓来”。作品通过茶馆老板王利发对祖传“裕泰茶馆”的惨淡经营。  

    2022年7月14日
    78
  • 修改WordPress上传文件大小限制方法

    今天要分享的方法就是增大文件上传限制的大小。不过如果你的主机商有特别严格的限制,本文的方法就不确定可以实现了。一般的主机商都可以实现。

    2021年5月1日
    49
  • win10新建宽带连接的步骤(win10怎么添加宽带连接)

    现在绝大多数情况大家都是使用光猫或者路由器登录,使用电脑直接连接宽带的情况确实比较少,并且系统默认也有对应的宽带连接~但是特殊情况下,如果需要的话下文可以指导操作新建一个宽带连接~ 首先,您需要您对应供应商提供的宽带账户密码,并且确认账户能正常使用。 您可以直接搜索拨号设置直接进入设置界面,当然您也可以使用开始,设置,网络和Internet,拨号的顺序进入设置。 进入界面后,选择设置新连接,新界面选择连接到Internet。 然后点击下一步,选择宽带PPPOE,并且在新界面输入宽带供应商提供的账户和密码,强烈建议勾选记住此密码选项,以便后续不输入密码快速连接。 确认输入正确后,点击底部的连接,等待连接即可,也可以在等待连接的界面点击跳过完成设置,后续需要使用的时候再连接~  

    2021年9月19日
    50