当前位置: 首页 > news >正文

TVM调度原语完全指南:从入门到微架构级优化

调度原语

在TVM的抽象体系中,调度(Schedule)是对计算过程的时空重塑。每一个原语都是改变计算次序、数据流向或并行策略的手术刀。其核心作用可归纳为:

优化目标 = max ⁡ ( 计算密度 内存延迟 × 指令开销 ) \text{优化目标} = \max \left( \frac{\text{计算密度}}{\text{内存延迟} \times \text{指令开销}} \right) 优化目标=max(内存延迟×指令开销计算密度)

下面我们将解剖20+个核心原语,揭示它们的运作机制与优化场景。


基础维度操作

1. split:维度的量子裂变

作用:将单个维度拆分为多个子维度,为后续优化创造空间

# 将长度128的维度拆分为(外轴, 内轴)=(16, 8)  
outer, inner = s[op].split(op.axis[0], factor=8)  
# 或者指定外层大小  
outer, inner = s[op].split(op.axis[0], nparts=16)  '''  
数学等价转换:  
原始迭代: for i in 0..127  
拆分后: for i_outer in 0..15  for i_inner in 0..7  i = i_outer * 8 + i_inner  
'''  

硬件视角

  • 当处理256-bit SIMD寄存器时,拆分成8个float32元素的分块可完美利用向量化
  • 在L1缓存为32KB的CPU上,拆分后的子块应满足:
    子块大小 × 数据类型大小 ≤ 32768 B \text{子块大小} \times \text{数据类型大小} \leq 32768B 子块大小×数据类型大小32768B

2. fuse:维度的熔合反应

作用:合并多个连续维度,简化循环结构

fused = s[op].fuse(op.axis[0], op.axis[1])  
'''  
数学等价:  
原始: for i in 0..15  for j in 0..31  
合并后: for fused in 0..511 (16*32=512)  
'''  

优化场景

  • 当相邻维度具有相同优化策略时,减少循环嵌套层数
  • 与parallel原语配合实现粗粒度并行
  • 案例:将H和W维度融合后做分块,更适合GPU线程块划分

3. reorder:维度的空间折叠

作用:重新排列循环轴的顺序

s[op].reorder(op.axis[2], op.axis[0], op.axis[1])  
'''  
原始顺序: axis0 -> axis1 -> axis2  
调整后: axis2 -> axis0 -> axis1  
'''  

硬件敏感优化

  • 将内存连续访问的维度置于内层循环
# 将通道维度移到最内层以利用向量化  
s[conv].reorder(n, h, w, c)  
  • 在GPU上将块索引维度提前以提升局部性
s[matmul].reorder(block_idx, thread_idx, inner)  

并行化武器库

4. parallel:多核并发的起搏器

作用:标记循环轴进行多线程并行

s[op].parallel(op.axis[0])  

实现机制

  • 在LLVM后端会生成OpenMP pragma指令
#pragma omp parallel for  
for (int i = 0; i < N; ++i)  

黄金法则

  • 并行粒度不宜过细(避免线程创建开销)
  • 每个线程的任务量应大于10μs
  • 案例:对batch维度做并行,每个线程处理不同样本

5. vectorize:SIMD的激活密钥

作用:将内层循环转换为向量化指令

s[op].vectorize(inner_axis)  

代码生成示例
原始标量计算:

for (int i = 0; i < 8; ++i)  C[i] = A[i] + B[i];  

向量化后(AVX2):

__m256 va = _mm256_load_ps(A);  
__m256 vb = _mm256_load_ps(B);  
__m256 vc = _mm256_add_ps(va, vb);  
_mm256_store_ps(C, vc);  

性能临界点

  • 向量化收益公式:
    加速比 = min ⁡ ( 元素数 向量宽度 , 内存带宽 ) \text{加速比} = \min\left(\frac{\text{元素数}}{\text{向量宽度}}, \text{内存带宽}\right) 加速比=min(向量宽度元素数,内存带宽)
  • 当循环长度不是向量宽度整数倍时,需尾部处理

6. bind:硬件线程的映射协议

作用:将循环轴绑定到硬件线程索引

block_x = tvm.thread_axis("blockIdx.x")  
s[op].bind(op.axis[0], block_x)  

GPU编程范式

  • blockIdx.x:GPU线程块索引
  • threadIdx.x:块内线程索引
  • 典型绑定策略:
    bx = tvm.thread_axis("blockIdx.x")  
    tx = tvm.thread_axis("threadIdx.x")  
    s[matmul].bind(s[matmul].op.axis[0], bx)  
    s[matmul].bind(s[matmul].op.axis[1], tx)  
    

CPU-GPU差异

  • CPU:通常绑定到OpenMP线程
  • GPU:需要精确管理线程层次结构

内存优化原语

7. compute_at:计算的时空折叠

作用:将一个阶段的计算插入到另一个阶段的指定位置

s[producer].compute_at(s[consumer], consumer_axis)  

优化效果

  • 提升数据局部性,减少中间结果存储
  • 案例:在卷积计算中,将输入加载插入到输出通道循环内

8. storage_align:内存对齐的标尺

作用:调整张量存储的内存对齐

s[op].storage_align(axis, factor, offset)  

底层原理

  • 确保数据地址满足:
    address % factor = = offset \text{address} \% \text{factor} == \text{offset} address%factor==offset
  • 典型用例:
    # 对齐到64字节边界(适合AVX-512)  
    s[input].storage_align(axis=2, factor=64, offset=0)  
    

性能影响

  • 对齐错误可导致性能下降10倍以上
  • 现代CPU对非对齐访问的惩罚已减小,但SIMD指令仍需对齐

9. cache_read/cache_write:数据的时空驿站

作用:创建数据的临时缓存副本

AA = s.cache_read(A, "shared", [B])  

GPU优化案例

# 将全局内存数据缓存到共享内存  
s[AA].compute_at(s[B], bx)  
s[AA].bind(s[AA].op.axis[0], tx)  

缓存层次选择

缓存类型硬件对应延迟周期
“local”寄存器1
“shared”GPU共享内存10-20
“global”设备内存200-400

循环优化原语

10. unroll:循环展开的时空折叠

作用:将循环体复制多份,消除分支预测开销

s[op].unroll(inner_axis)  

代码生成对比
原始循环:

for (int i = 0; i < 4; ++i) {  C[i] = A[i] + B[i];  
}  

展开后:

C[0] = A[0] + B[0];  
C[1] = A[1] + B[1];  
C[2] = A[2] + B[2];  
C[3] = A[3] + B[3];  

收益递减点

  • 循环体过大会导致指令缓存压力
  • 经验公式:
    最佳展开因子 = L1 ICache Size 循环体代码大小 \text{最佳展开因子} = \sqrt{\frac{\text{L1 ICache Size}}{\text{循环体代码大小}}} 最佳展开因子=循环体代码大小L1 ICache Size

11. pragma:编译器的微观调控

作用:插入特定编译指导语句

s[op].pragma(axis, "unroll_and_jam", 4)  

常见Pragma指令

# 强制向量化  
s[op].pragma(axis, "vectorize", 8)  # 流水线并行  
s[op].pragma(axis, "software_pipeline", 3)  # 内存预取  
s[op].pragma(axis, "prefetch", A)  

架构特定优化

  • Intel CPU:
    s[op].pragma(axis, "ivdep")  # 忽略向量依赖  
    
  • NVIDIA GPU:
    s[op].pragma(axis, "ldg", 1)  # 使用__ldg指令  
    

张量计算原语

12. tensorize:硬件指令的直通车

作用:将计算模式映射到特定硬件指令

# 定义矩阵内积的Tensorize内核  
def dot_product_4x4():  # 此处定义计算规则  pass  s[matmul].tensorize(ci, dot_product_4x4)  

硬件案例

  • Intel VNNI:4x4矩阵乘指令
  • NVIDIA Tensor Core:混合精度矩阵运算
  • ARM SVE:可伸缩向量扩展

性能收益

  • 在兼容硬件上可获得10-100倍加速
  • 需要精确匹配计算模式和数据布局

高级组合原语

13. rfactor:归约计算的时空分裂

作用:将归约操作分解为多阶段计算

# 原始归约  
C = tvm.compute((n,), lambda i: tvm.sum(A[i,j], axis=j))  # 创建rfactor阶段  
_, ki = s[C].split(s[C].op.reduce_axis[0], factor=4)  
Crf = s.rfactor(C, ki)  

数学等价性
原始:
C [ i ] = ∑ j = 0 15 A [ i , j ] C[i] = \sum_{j=0}^{15} A[i,j] C[i]=j=015A[i,j]
分解后:
C r f [ i , k ] = ∑ j = 0 3 A [ i , 4 k + j ] C [ i ] = ∑ k = 0 3 C r f [ i , k ] Crf[i,k] = \sum_{j=0}^{3} A[i,4k+j] \\ C[i] = \sum_{k=0}^{3} Crf[i,k] Crf[i,k]=j=03A[i,4k+j]C[i]=k=03Crf[i,k]

优化场景

  • 提升归约操作的并行度
  • 减少原子操作冲突(GPU)

14. compute_inline:计算的时空湮灭

作用:将中间计算结果直接内联到消费者

s[B].compute_inline()  

代码变换
内联前:

B = A + 1  
C = B * 2  

内联后:

C = (A + 1) * 2  

权衡分析

  • 优点:减少内存占用,提升局部性
  • 缺点:可能增加重复计算量

架构特定原语

15. stencil:数据流动的模板

作用:定义滑动窗口式计算模式

with tvm.stencil.grid([H, W]) as [i, j]:  B[i,j] = A[i-1,j] + A[i+1,j] + A[i,j-1] + A[i,j+1]  

硬件映射

  • FPGA:生成流水线化数据流
  • GPU:映射到共享内存的滑窗缓存
  • CPU:自动生成SIMD优化代码

16. sparse:稀疏数据的压缩艺术

作用:处理稀疏张量计算

# 定义CSR格式稀疏矩阵  
indptr = tvm.placeholder((n+1,), dtype="int32")  
indices = tvm.placeholder((nnz,), dtype="int32")  
data = tvm.placeholder((nnz,), dtype="float32")  # 稀疏矩阵乘调度  
s = tvm.create_schedule([indptr, indices, data, dense])  
s.sparse_indices(indptr, indices)  

优化技巧

  • 使用行分块减少随机访问
  • 利用向量化处理非零元素
  • 案例:在Transformer模型中优化稀疏注意力计算

调试与剖析原语

17. debug:计算图的显微镜

作用:输出中间计算步骤详情

s[op].debug()  

输出示例

Compute stage:  for (i, 0, 16) {  for (j, 0, 32) {  C[i, j] = (A[i, j] + B[i, j])  }  }  

调试技巧

  • 结合TVM的Lower函数查看IR变更
  • 使用LLDB/GDB附加到编译过程

18. profile:性能的时空计量仪

作用:插入性能剖析代码

s[op].profile()  

输出信息

  • 循环迭代次数
  • 缓存命中率
  • 指令吞吐量
  • 案例:发现某个循环存在90%的缓存未命中

未来原语展望

19. auto_tensorize:AI优化AI

作用:自动匹配硬件指令模式

s.auto_tensorize(target="avx512")  

实现原理

  • 使用机器学习模型识别可优化的计算模式
  • 自动生成tensorize内核

20. quantum:量子计算接口

作用:映射到量子计算指令

s[op].quantum(gate="H", qubits=[0,1])  

前沿领域

  • 量子神经网络优化
  • 混合经典-量子调度

原语组合艺术

优化案例:三维卷积调度策略

# 定义计算  
data = tvm.placeholder((N, C, D, H, W), "float32")  
kernel = tvm.placeholder((K, C, KD, KH, KW), "float32")  
conv3d = topi.nn.conv3d_ndhwc(data, kernel)  # 创建调度  
s = tvm.create_schedule(conv3d.op)  # 分块策略  
n, d, h, w, k = conv3d.op.axis  
dn, di = s[conv3d].split(d, factor=2)  
hn, hi = s[conv3d].split(h, factor=4)  
wn, wi = s[conv3d].split(w, factor=4)  
s[conv3d].reorder(n, dn, hn, wn, di, hi, wi, k)  # 并行化  
s[conv3d].parallel(n)  # 向量化  
s[conv3d].vectorize(wi)  # 缓存优化  
AA = s.cache_read(data, "local", [conv3d])  
WW = s.cache_read(kernel, "local", [conv3d])  
s[AA].compute_at(s[conv3d], wn)  
s[WW].compute_at(s[conv3d], wn)  # 指令级优化  
s[conv3d].unroll(hi)  
s[conv3d].pragma(dn, "prefetch", AA)  

结语:调度原语的哲学

在TVM的世界里,每一个调度原语都是时空的雕塑工具。优秀的性能工程师需要兼具:

  • 微观直觉:理解每个原语在硬件底层的映射
  • 宏观视野:把握多个原语之间的相互作用
  • 艺术感知:在约束条件下找到优雅的优化路径

正如计算机图形学中的渲染方程,调度优化也是一个积分过程:

最优性能 = ∫ 硬件空间 ∏ 原语 f ( x ) d x \text{最优性能} = \int_{\text{硬件空间}} \prod_{\text{原语}} f(x) \, dx 最优性能=硬件空间原语f(x)dx

愿每一位读者都能在TVM的调度世界中,找到属于自己的优化之美。

相关文章:

TVM调度原语完全指南:从入门到微架构级优化

调度原语 在TVM的抽象体系中&#xff0c;调度&#xff08;Schedule&#xff09;是对计算过程的时空重塑。每一个原语都是改变计算次序、数据流向或并行策略的手术刀。其核心作用可归纳为&#xff1a; 优化目标 max ⁡ ( 计算密度 内存延迟 指令开销 ) \text{优化目标} \max…...

c语言进阶(简单的函数 数组 指针 预处理 文件 结构体)

c语言补充 格式 void函数头 {} 中的是函数体 sum函数名 &#xff08;&#xff09; 参数表 #include <stdio.h>void sum(int begin, int end) {int i;int sum 0;for (i begin ; i < end ; i) {sum i;}printf("%d到%d的和是%d\n", begin, end, sum); …...

终极版已激活!绿话纯净,打开即用!!!

今天我想和大家聊聊一个非常实用的工具——视频转换大师最终版。 视频转换大师终极版&#xff0c;堪称一款全能型的视频制作神器&#xff0c;集视频转换与编辑功能于一体。它搭载的视频增强器技术&#xff0c;能够最大限度地保留原始视频质量&#xff0c;甚至还能实现质量的进…...

Vue.js 什么是 Composition API?

Vue.js 什么是 Composition API&#xff1f; 今天我们来聊聊 Vue 3 引入的一个重要特性&#xff1a;组合式 API&#xff08;Composition API&#xff09;。如果你曾在开发复杂的 Vue 组件时感到代码难以维护&#xff0c;那么组合式 API 可能正是你需要的工具。 什么是组合式 …...

MySQL高可用

一、mysql路由 1.利用路由器的连接路由特性&#xff0c;用户可以编写应用程序来连接到路由器&#xff0c;并令路由器使用响应的路由策略来处理连接来使其连接到正确的mysql数据库服务器 2.mysql route的部署方式 需要在所有数据库主机之外再打开一台主机mysql-router 配置mysql…...

30.Word:设计并制作新年贺卡以及标签【30】

目录 NO1.2 NO3邮件合并-信函 NO4邮件合并-标签​ NO1.2 另存为/F12&#xff1a;考生文件夹&#xff1a;Word.docx布局→页面设置对话框→页边距&#xff1a;上下左右→纸张&#xff1a;宽度/高度&#xff08;先调页边距&#x1f197;&#xff09;设计→页面颜色→填充效果→…...

Flink2支持提交StreamGraph到Flink集群

最近研究Flink源码的时候&#xff0c;发现Flink已经支持提交StreamGraph到集群了&#xff0c;替换掉了原来的提交JobGraph。 新增ExecutionPlan接口&#xff0c;将JobGraph和StreamGraph作为实现。 Flink集群Dispatcher也进行了修改&#xff0c;从JobGraph改成了接口Executio…...

大模型本地化部署(Ollama + Open-WebUI)

文章目录 环境准备下载Ollama模型下载下载Open-WebUI 本地化部署的Web图形化界面本地模型联网查询安装 Docker安装 SearXNG本地模型联网查询 环境准备 下载Ollama 下载地址&#xff1a;Ollama网址 安装完成后&#xff0c;命令行里执行命令 ollama -v查看是否安装成功。安装成…...

C++哈希(链地址法)(二)详解

文章目录 1.开放地址法1.1key不能取模的问题1.1.1将字符串转为整型1.1.2将日期类转为整型 2.哈希函数2.1乘法散列法&#xff08;了解&#xff09;2.2全域散列法&#xff08;了解&#xff09; 3.处理哈希冲突3.1线性探测&#xff08;挨着找&#xff09;3.2二次探测&#xff08;跳…...

IME关于输入法横屏全屏显示问题-Android14

IME关于输入法横屏全屏显示问题-Android14 1、输入法全屏模式updateFullscreenMode1.1 全屏模式判断1.2 全屏模式布局设置 2、应用侧关闭输入法全屏模式2.1 调用输入法的应用设置flag2.2 继承InputMethodService.java的输入法应用覆盖onEvaluateFullscreenMode方法 InputMethod…...

网络工程师 (11)软件生命周期与开发模型

一、软件生命周期 前言 软件生命周期&#xff0c;也称为软件开发周期或软件开发生命周期&#xff0c;是指从软件项目的启动到软件不再被使用为止的整个期间。这个过程可以细分为多个阶段&#xff0c;每个阶段都有其特定的目标、任务和产出物。 1. 问题定义与需求分析 问题定义…...

【人工智能】基于Python的机器翻译系统,从RNN到Transformer的演进与实现

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 机器翻译(Machine Translation, MT)作为自然语言处理领域的重要应用之一,近年来受到了广泛的关注。在本篇文章中,我们将详细探讨如何使…...

网络工程师 (12)软件开发与测试

一、软件设计 &#xff08;一&#xff09;定义与目的 软件设计是从软件需求出发&#xff0c;设计软件的整体结构、功能模块、实现算法及编写代码的过程&#xff0c;旨在确定系统如何完成预定任务。其目标是确保目标系统能够抽象、普遍地完成预定任务&#xff0c;并为后续的软件…...

3.Spring-事务

一、隔离级别&#xff1a; 脏读&#xff1a; 一个事务访问到另外一个事务未提交的数据。 不可重复读&#xff1a; 事务内多次查询相同条件返回的结果不同。 幻读&#xff1a; 一个事务在前后两次查询同一个范围的时候&#xff0c;后一次查询看到了前一次查询没有看到的行。 二…...

Python字典详解:从入门到实践

Python字典详解&#xff1a;从入门到实践 字典&#xff08;Dictionary&#xff09;是Python中最重要且最常用的数据结构之一。本文将深入讲解字典的特性、操作方法和实际应用案例。 1. 字典简介 字典是可变的、无序的键值对集合&#xff0c;使用{}创建。每个元素由key: valu…...

91,【7】 攻防世界 web fileclude

进入靶场 <?php // 包含 flag.php 文件 include("flag.php");// 以高亮语法显示当前文件&#xff08;即包含这段代码的 PHP 文件&#xff09;的内容 // 方便查看当前代码结构和逻辑&#xff0c;常用于调试或给解题者提示代码信息 highlight_file(__FILE__);// 检…...

41【文件名的编码规则】

我们在学习的过程中&#xff0c;写出数据或读取数据时需要考虑编码类型 火山采用&#xff1a;UTF-16 易语言采用&#xff1a;GBK php采用&#xff1a;UTF-8 那么我们写出的文件名应该是何种编码的&#xff1f;比如火山程序向本地写出一个“测试.txt”&#xff0c;理论上这个“测…...

蓝桥杯备赛经验帖

蓝桥杯备赛经验帖 作者&#xff1a;blue 时间&#xff1a;2025.2.1 文章目录 蓝桥杯备赛经验帖1.为什么有这篇文章2.赛制3.比赛流程4.如何准备5.其他建议6.一些感悟 1.为什么有这篇文章 ​ 笔者近期发现&#xff0c;观看我写的两道第十五届蓝桥杯题解的人数逐渐增多&#xf…...

一文大白话讲清楚webpack基本使用——17——Tree Shaking

文章目录 一文大白话讲清楚webpack基本使用——17——Tree Shaking1. 建议按文章顺序从头看&#xff0c;一看到底&#xff0c;豁然开朗2. 啥叫Tree Shaking3. 什么是死代码&#xff0c;怎么来的3. Tree Shaking的流程3.1 标记3.2 利用Terser摇起来 4. 具体使用方式4.1 适用前提…...

【C++ 区间位运算】3209. 子数组按位与值为 K 的数目|2050

本文涉及知识点 位运算、状态压缩、枚举子集汇总 LeetCode3209. 子数组按位与值为 K 的数目 给你一个整数数组 nums 和一个整数 k &#xff0c;请你返回 nums 中有多少个子数组 满足&#xff1a;子数组中所有元素按位 AND 的结果为 k 。 示例 1&#xff1a; 输入&#xff1a…...

8 比例缩放(scale.rs)

scale.rs代码是几何变换库euclid中典型的数据结构和方法的例子&#xff0c;用于处理二维和三维空间中的缩放变换。 一、scale.rs文件源码 //! A type-checked scaling factor between units.use crate::num::One;use crate::approxord::{max, min}; use crate::{Box2D, Box3D…...

二分 机器人的跳跃问题

二段性:找到一个值&#xff0c;大于此值的时候都成立&#xff0c;小于的时候都不成立 更新的方式只有两种&#xff0c;左边的mid更新不需要1&#xff1b;右边的mid更新需要1 //对能量进行二分&#xff0c;确定能量的范围 //特判防止溢出int #include<bits/stdc.h> using…...

Hive:复杂数据类型之Map函数

Map函数 是Hive里面的一种复杂数据类型, 用于存储键值对集合。Map中的键和值可以是基础类型或复合类型&#xff0c;这使得Map在处理需要关联存储信息的数据时非常有用。 定义map时,需声明2个属性: key 和 value , map中是 key value 组成一个元素 key-value, key必须为原始类…...

R 字符串:深入理解与高效应用

R 字符串:深入理解与高效应用 引言 在R语言中,字符串是数据处理和编程中不可或缺的一部分。无论是数据清洗、数据转换还是数据分析,字符串的处理都是基础技能。本文将深入探讨R语言中的字符串概念,包括其基本操作、常见函数以及高效应用方法。 字符串基本概念 字符串定…...

设计模式Python版 桥接模式

文章目录 前言一、桥接模式二、桥接模式示例三、桥接模式与适配器模式的联用 前言 GOF设计模式分三大类&#xff1a; 创建型模式&#xff1a;关注对象的创建过程&#xff0c;包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式和建造者模式。结构型模式&…...

记5(一元逻辑回归+线性分类器+多元逻辑回归

目录 1、一元逻辑回归2、线性可分&线性不可分3、Iris数据集实现多元逻辑回归4、绘制分类图5、鸢尾花分类图6、多分类问题&#xff1a;&#xff08;softmax回归&#xff09;6.1、编码&#xff1a;自然顺序码、独热编码、独冷编码6.2、二/多分类问题&#xff1a;6.3、softmax…...

【Python】第七弹---Python基础进阶:深入字典操作与文件处理技巧

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【MySQL】【Python】 目录 1、字典 1.1、字典是什么 1.2、创建字典 1.3、查找 key 1.4、新增/修改元素 1.5、删除元素 1.6、遍历…...

Nginx 运维开发高频面试题详解

一、基础核心问题 原文链接&#xff1a;https://blog.csdn.net/weixin_51146329/article/details/142963853 1、什么是Nginx&#xff1f; Nginx 是一个高性能的 HTTP 和反向代理服务器&#xff0c;它以轻量级和高并发处理能力而闻名。Nginx 的反向代理功能允许它作为前端服务…...

下载OpenJDK

由于Oracle需要付费&#xff0c;并且之前我在寻找openJDK的时候&#xff0c;我不知道网址&#xff0c;并且也不知道在这个openjdk这个网址里点击哪个模块进行下载。最近我在看虚拟机相关的书籍的时候&#xff0c;找到了相关的网址。 注意&#xff1a;下面的下载都是基于可以科…...

Web3.js详解

Web1&Web2&Web3 以下是Web1、Web2和Web3的详细介绍&#xff0c;以及一个对比表格&#xff1a; Web1 定义&#xff1a;Web1指的是有着固定内容的非许可的开源网络。特点&#xff1a;在Web1时代&#xff0c;网站内容主要由网站管理员或创建者提供&#xff0c;用户只能…...