【TVM 学习资料】用 Schedule 模板和 AutoTVM 优化算子

2023-11-08

本篇文章译自英文文档 Optimizing Operators with Schedule Templates and AutoTVM,作者是 Lianmin Zheng,Chris Hoge。更多 TVM 中文文档,访问→TVM 中文站

本教程将展示如何用 TVM 张量表达式(TE)语言编写 schedule 模板,并通过 AutoTVM 对模板进行搜索,从而找到最佳 schedule。这个自动优化张量计算的过程被称为 Auto-Tuning。

本教程基于前面的 TE 编写矩阵乘法教程 设立。

auto-tuning 包括两个步骤:

第一步:定义搜索空间。
第二步:运行搜索算法来探索这个空间。
通过本教程可以了解如何在 TVM 中执行这两个步骤。整个工作流程由一个矩阵乘法示例来说明。

备注
注意,本教程不会在 Windows 或最新版本的 macOS 上运行。如需运行,请将本教程的主体放在 if name == “main”: 代码块中。

安装依赖

要在 TVM 中使用 autotvm 包,需安装一些额外的依赖。

pip3 install --user psutil xgboost cloudpickle

为了让 TVM 在调优过程中运行更快,建议使用 Cython 作为 TVM 的 FFI。在 TVM 的根目录下,执行:

pip3 install --user cython
sudo make cython3

现在我们一起来看如何用 Python 代码实现。首先导入所需的包:

import logging
import sys

import numpy as np
import tvm
from tvm import te
import tvm.testing

# 模块名叫 `autotvm`
from tvm import autotvm

TE 的基本矩阵乘法

回想一下用 TE 进行矩阵乘法的基本实现,下面做一些改变。将矩阵乘法放在 Python 函数定义中。简单起见,重点关注拆分的优化,将重新排序的块大小设为固定值。

def matmul_basic(N, L, M, dtype):

    A = te.placeholder((N, L), name="A", dtype=dtype)
    B = te.placeholder((L, M), name="B", dtype=dtype)

    k = te.reduce_axis((0, L), name="k")
    C = te.compute((N, M), lambda i, j: te.sum(A[i, k] * B[k, j], axis=k), name="C")
    s = te.create_schedule(C.op)

    # 调度
    y, x = s[C].op.axis
    k = s[C].op.reduce_axis[0]

    yo, yi = s[C].split(y, 8)
    xo, xi = s[C].split(x, 8)

    s[C].reorder(yo, xo, k, yi, xi)

    return s, [A, B, C]

用 AutoTVM 进行矩阵乘法

前面的调度代码用常量“8”作为循环切分因子,但是它可能不是最佳的。因为最佳的循环切分因子取决于真实的硬件环境和输入 shape。

如果希望调度代码能够在更广泛的输入 shape 和目标硬件上可移植,最好定义一组候选值,并根据目标硬件上的评估结果选择最佳值。

autotvm 中可以为这种值定义一个可调参数,或者一个 “knob”。

基本矩阵乘法模板

以下示例将演示,如何为 split 调度操作的 block 大小创建一个可调的参数集。

# Matmul V1: 列出候选值
@autotvm.template("tutorial/matmul_v1")  # 1. 使用装饰器
def matmul_v1(N, L, M, dtype):
    A = te.placeholder((N, L), name="A", dtype=dtype)
    B = te.placeholder((L, M), name="B", dtype=dtype)

    k = te.reduce_axis((0, L), name="k")
    C = te.compute((N, M), lambda i, j: te.sum(A[i, k] * B[k, j], axis=k), name="C")
    s = te.create_schedule(C.op)

    # 调度
    y, x = s[C].op.axis
    k = s[C].op.reduce_axis[0]

    # 2. 获取 config 对象
    cfg = autotvm.get_config()

    # 3. 定义搜索空间
    cfg.define_knob("tile_y", [1, 2, 4, 8, 16])
    cfg.define_knob("tile_x", [1, 2, 4, 8, 16])

    # 4. 根据 config 进行调度
    yo, yi = s[C].split(y, cfg["tile_y"].val)
    xo, xi = s[C].split(x, cfg["tile_x"].val)

    s[C].reorder(yo, xo, k, yi, xi)

    return s, [A, B, C]

下面将对前面的调度代码作出四个修改,然后得到一个可调的“模板”。一一解释这些修改:

  1. 使用装饰器将此函数标记为简单模板。

  2. 获取 config 对象:将 cfg 视为此函数的参数,但我们以另外的方式获取它。cfg 参数使得这个函数不再是一个确定的 schedule。将不同的配置传递给这个函数,可以得到不同的 schedule。这种使用配置对象的函数称为“模板”。

为使模板函数更精炼,可在单个函数中定义参数搜索空间:

  • 用一组值来定义搜索空间。将 cfg 转为 ConfigSpace 对象,收集此函数中的所有可调 knob,然后从中构建一个搜索空间。
  • 根据空间中的实体进行调度。将 cfg 转为 ConfigEntity 对象,当它被转为 ConfigEntity 后,会忽略所有空间定义
    API(即 cfg.define_XXXXX(…)),但会存储所有可调 knob 的确定值,并根据这些值进行调度。

在 auto-tuning 的过程中,首先用 ConfigSpace 对象调用这个模板来构建搜索空间,然后在构建的空间中用不同的 ConfigEntity 调用这个模板,来得到不同的 schedule。最后,我们将评估由不同 schedule 生成的代码,然后选择最佳的 schedule。

  1. 定义两个可调 knob。第一个是 tile_y,它有 5 个可能值。第二个是 tile_x,它和前者具有相同的可能值。这两个 knob 是独立的,所以它们跨越大小为 25 = 5x5 的搜索空间。

  2. 配置 knob 被传递给 split 调度操作,然后可以根据之前在 cfg 中定义的 5x5 确定值进行调度。

带有高级参数 API 的矩阵乘法模板

前面的模板手动列出了 konb 的所有可能值,它是用来定义空间的最底层 API,显示列出了要搜索的参数空间。这里推荐使用另一组更高级的 API,它可以更简单、更智能地定义搜索空间。

下面的示例用 ConfigSpace.define_split 来定义拆分 knob。它列举了所有可能的拆分 axis 和构造空间的方法。

同时,ConfigSpace.define_reorder 用于对 knob 重新排序,ConfigSpace.define_annotate 用于对展开、向量化、线程绑定等进行注释 。当高级 API 无法满足你的需求时,可以回退使用底层 API。

@autotvm.template("tutorial/matmul")
def matmul(N, L, M, dtype):
    A = te.placeholder((N, L), name="A", dtype=dtype)
    B = te.placeholder((L, M), name="B", dtype=dtype)

    k = te.reduce_axis((0, L), name="k")
    C = te.compute((N, M), lambda i, j: te.sum(A[i, k] * B[k, j], axis=k), name="C")
    s = te.create_schedule(C.op)

    # 调度
    y, x = s[C].op.axis
    k = s[C].op.reduce_axis[0]

    ##### 开始定义空间 #####
    cfg = autotvm.get_config()
    cfg.define_split("tile_y", y, num_outputs=2)
    cfg.define_split("tile_x", x, num_outputs=2)
    ##### 结束定义空间 #####

    # 根据 config 进行调度
    yo, yi = cfg["tile_y"].apply(s, C, y)
    xo, xi = cfg["tile_x"].apply(s, C, x)

    s[C].reorder(yo, xo, k, yi, xi)

    return s, [A, B, C]

关于 cfg.define_split 的更多解释 在此模板中,cfg.define_split(“tile_y”, y,
num_outputs=2) 枚举了所有可能的组合(以 y 的长度为因子,将 y 轴分成两个轴)。例如,如果 y 的长度为 32 并且想以
32 为因子将它拆分为两个轴,那么(外轴长度,内轴长度)有 6 个可能的值,即 (32, 1),(16, 2),(8, 4),(4,
8),(2, 16) 或 (1, 32)。这些也是 tile_y 的 6 个可能值。

调度过程中,cfg[“tile_y”] 是一个 SplitEntity 对象。我们将外轴和内轴的长度存储在
cfg[‘tile_y’].size (有两个元素的元组)中。这个模板使用 yo, yi = cfg[‘tile_y’].apply(s,
C, y) 来应用它。其实等价于 yo, yi = s[C].split(y, cfg[“tile_y”].size[1]) 或 yo,
yi = s[C].split(y, nparts=cfg['tile_y"].size[0])。

cfg.apply API 的优点是它使多级拆分(即当 num_outputs >= 3 时)变得更加简单。

第 2 步:使用 AutoTVM 优化矩阵乘法

第 1 步编写的矩阵乘法模板,可对拆分的 schedule 中的块大小进行参数化。通过第 1 步,可以实现对这个参数空间进行搜索。下一步是选择一个调优器来指导如何对空间进行探索。

TVM 的自动调优器

调优器的任务可用以下伪代码来描述:

ct = 0
while ct < max_number_of_trials:
    propose a batch of configs
    measure this batch of configs on real hardware and get results
    ct += batch_size

调优器可采取不同的策略来计划下一批配置,包括:

  • tvm.autotvm.tuner.RandomTuner :以随机顺序枚举空间

  • tvm.autotvm.tuner.GridSearchTuner :以网格搜索顺序枚举空间

  • tvm.autotvm.tuner.GATuner :使用遗传算法搜索空间

  • tvm.autotvm.tuner.XGBTuner :用基于模型的方法训练一个 XGBoost 模型,来预测降级 IR
    的速度,并根据预测值选择下一批配置。

可根据空间大小、时间预算和其他因素来选择调优器。例如,如果你的空间非常小(小于 1000),则网格搜索调优器或随机调优器就够了。如果你的空间在 10^9 级别(CUDA GPU 上的 conv2d 算子的空间大小),XGBoostTuner 可以更有效地探索并找到更好的配置。

开始调优

下面继续矩阵乘法的示例。首先创建一个调优任务,然后检查初始的搜索空间。下面示例中是 512x512 的矩阵乘法,空间大小为 10x10=100。注意,任务和搜索空间与选择的调优器无关。

N, L, M = 512, 512, 512
task = autotvm.task.create("tutorial/matmul", args=(N, L, M, "float32"), target="llvm")
print(task.config_space)

输出结果:

ConfigSpace (len=100, space_map=
   0 tile_y: Split(policy=factors, product=512, num_outputs=2) len=10
   1 tile_x: Split(policy=factors, product=512, num_outputs=2) len=10
)

然后定义如何评估生成的代码,并且选择一个调优器。由于我们的空间很小,所以随机调优器就可以。

本教程只做 10 次试验进行演示。实际上可以根据自己的时间预算进行更多试验。调优结果会记录到日志文件中。这个文件可用于选择之后发现的调优器的最佳配置。

# 记录 config(为了将 tuning 日志打印到屏幕)
logging.getLogger("autotvm").setLevel(logging.DEBUG)
logging.getLogger("autotvm").addHandler(logging.StreamHandler(sys.stdout))

评估配置有两个步骤:构建和运行。默认用所有 CPU core 来编译程序。然后依次进行评估。为了减少方差,对 5 次评估结果取平均值。

measure_option = autotvm.measure_option(builder="local", runner=autotvm.LocalRunner(number=5))

# 用 RandomTuner 开始调优, 日志记录到 `matmul.log` 文件中
# 可用 XGBTuner 来替代.
tuner = autotvm.tuner.RandomTuner(task)
tuner.tune(
    n_trial=10,
    measure_option=measure_option,
    callbacks=[autotvm.callback.log_to_file("matmul.log")],
)

输出结果:

waiting for device...
device available
Get devices for measurement successfully!
No: 1   GFLOPS: 8.48/8.48       result: MeasureResult(costs=(0.0316434228,), error_no=MeasureErrorNo.NO_ERROR, all_cost=0.638512134552002, timestamp=1657225928.6342561)        [('tile_y', [-1, 1]), ('tile_x', [-1, 256])],None,80
No: 2   GFLOPS: 2.30/8.48       result: MeasureResult(costs=(0.1165478966,), error_no=MeasureErrorNo.NO_ERROR, all_cost=2.0105199813842773, timestamp=1657225930.6636436)       [('tile_y', [-1, 4]), ('tile_x', [-1, 8])],None,32
No: 3   GFLOPS: 11.82/11.82     result: MeasureResult(costs=(0.0227097348,), error_no=MeasureErrorNo.NO_ERROR, all_cost=0.5589795112609863, timestamp=1657225931.7059512)       [('tile_y', [-1, 64]), ('tile_x', [-1, 32])],None,56
No: 4   GFLOPS: 1.66/11.82      result: MeasureResult(costs=(0.1616202114,), error_no=MeasureErrorNo.NO_ERROR, all_cost=2.6911513805389404, timestamp=1657225934.9635096)       [('tile_y', [-1, 1]), ('tile_x', [-1, 4])],None,20
No: 5   GFLOPS: 3.65/11.82      result: MeasureResult(costs=(0.073561817,), error_no=MeasureErrorNo.NO_ERROR, all_cost=1.3051848411560059, timestamp=1657225936.3988533)        [('tile_y', [-1, 256]), ('tile_x', [-1, 16])],None,48
No: 6   GFLOPS: 1.85/11.82      result: MeasureResult(costs=(0.1452834464,), error_no=MeasureErrorNo.NO_ERROR, all_cost=2.5179028511047363, timestamp=1657225938.961955)        [('tile_y', [-1, 512]), ('tile_x', [-1, 4])],None,29
No: 7   GFLOPS: 0.87/11.82      result: MeasureResult(costs=(0.30933780240000003,), error_no=MeasureErrorNo.NO_ERROR, all_cost=5.067087888717651, timestamp=1657225944.589149)  [('tile_y', [-1, 512]), ('tile_x', [-1, 2])],None,19
No: 8   GFLOPS: 10.53/11.82     result: MeasureResult(costs=(0.025489421,), error_no=MeasureErrorNo.NO_ERROR, all_cost=0.5452830791473389, timestamp=1657225945.1592515)        [('tile_y', [-1, 4]), ('tile_x', [-1, 64])],None,62
No: 9   GFLOPS: 1.58/11.82      result: MeasureResult(costs=(0.16960762680000002,), error_no=MeasureErrorNo.NO_ERROR, all_cost=2.8109781742095947, timestamp=1657225948.0900776)        [('tile_y', [-1, 2]), ('tile_x', [-1, 2])],None,11
No: 10  GFLOPS: 2.42/11.82      result: MeasureResult(costs=(0.11083148779999999,), error_no=MeasureErrorNo.NO_ERROR, all_cost=1.8757600784301758, timestamp=1657225950.0266354)        [('tile_y', [-1, 4]), ('tile_x', [-1, 4])],None,22

调优完成后,可从日志文件中选择具有最佳评估性能的配置,并用相应参数来编译 schedule。快速验证 schedule 是否产生了正确的结果,可直接在 autotvm.apply_history_best 上下文中调用 matmul 函数,它会用参数查询调度上下文,然后可用相同的参数获取最优配置。

# 从日志文件中应用历史最佳
with autotvm.apply_history_best("matmul.log"):
    with tvm.target.Target("llvm"):
        s, arg_bufs = matmul(N, L, M, "float32")
        func = tvm.build(s, arg_bufs)

# 验证正确性
a_np = np.random.uniform(size=(N, L)).astype(np.float32)
b_np = np.random.uniform(size=(L, M)).astype(np.float32)
c_np = a_np.dot(b_np)

c_tvm = tvm.nd.empty(c_np.shape)
func(tvm.nd.array(a_np), tvm.nd.array(b_np), c_tvm)

tvm.testing.assert_allclose(c_np, c_tvm.numpy(), rtol=1e-4)

输出结果:

Finish loading 10 records

总结

本教程展示了如何构建算子模板,使得 TVM 能够搜索参数空间,并选择优化的调度配置。为了更深入地了解其工作原理,推荐基于 :ref: 张量表达式入门 <tensorexpr_get_started> 教程中演示的调度操作,向调度添加新的搜索参数。接下来的章节将演示 AutoScheduler,它是TVM 中一种优化常用算子的方法,同时无需用户提供自定义的模板。

下载 Python 源代码
下载 Jupyter Notebook

以上就是该文档的全部内容,查看更多 TVM 中文文档,请访问→TVM 中文站

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【TVM 学习资料】用 Schedule 模板和 AutoTVM 优化算子 的相关文章

  • 用CHAT如何写大学生会计综合模拟实训报告

    CHAT回复 标题 大学生会计综合模拟实训报告 一 前言 随着信息化时代的发展 现代会计工作不再只依赖手动运算和记录 而是更加倚重电脑软件系统的配合运用 因此 对我们大学生来说 把握会计理论知识的同时 积极掌握相关的实践应用技能变得非常重要
  • 如何利用CHAT做简单的总结体会?

    问CHAT 在测试过程中使用appium python自动化的优点和体会 CHAT回复 使用 Appium 配合 Python 进行自动化测试主要有以下几点优点 1 跨平台性 Appium 支持 iOS 和 Android 平台的应用自动化
  • 利用CHAT写个easywechat4 支付回调代码

    CHAT回复 以下是 EasyWechat4 的支付回调处理的基本代码 这个代码需要放置在你的后端服务器中 主要用于接收微信支付平台发送过来的支付结果通知 php
  • 文档扫描与矫正-仿射变换

    图像变换是计算机视觉和图像处理中的关键技术之一 它允许我们对图像进行各种形式的变形 调整和校正 其中 仿射变换是一种常见的变换方式 在文档扫描过程中 由于拍摄角度和畸变等原因 文档图像可能存在一定程度的形变 仿射变换可以用于校正文档图像 使
  • 深度好文:最全的大模型 RAG 技术概览

    本文是对检索增强生成 Retrieval Augmented Generation RAG 技术和算法的全面研究 对各种方法进行了系统性的梳理 涉及了 RAG 流程中的数据拆分 向量化 查询重写 查询路由等等 在做 RAG 的小伙伴一定知道
  • 用通俗易懂的方式讲解:如何用大语言模型构建一个知识问答系统

    传统搜索系统基于关键字匹配 在面向 游戏攻略 技术图谱 知识库等业务场景时 缺少对用户问题理解和答案二次处理能力 本文探索使用大语言模型 Large Language Model LLM 通过其对自然语言理解和生成的能力 揣摩用户意图 并对
  • 【信道估计】【MIMO】【FBMC】未来移动通信的滤波器组多载波调制方案(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码及文章
  • 【图像融合】基于联合双边滤波和局部梯度能量的多模态医学图像融合研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码 图像 文章
  • 【图像融合】基于联合双边滤波和局部梯度能量的多模态医学图像融合研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码 图像 文章
  • 问CHAT很繁琐的问题会不会有答案呢?

    问CHAT 什么已有的基于极值理论的极端温度重现期主要针对极端高温事件 对极端低温事件研究较少 CHAT 回复 为这主要可能是由于以下几个原因 1 气候变化与全球变暖 当前 全球变暖和气候变化的问题备受关注 这导致科研者更加关注极端高温事件
  • 喜报|华测导航荣获“张江之星”领军型企业称号

    近日 2023年度 张江之星 企业培育名单发布 上海华测导航荣获2023年度 张江之星 领军型企业称号 据悉 张江之星 企业培育是上海科创办为落实 关于推进张江高新区改革创新发展建设世界领先科技园区的若干意见 张江高新区加快世界领先科技园区
  • socket网络编程几大模型?看看CHAT是如何回复的?

    CHAT回复 网络编程中常见的有以下几种模型 1 阻塞I O模型 Blocking I O 传统的同步I O模型 一次只处理一个请求 2 非阻塞I O模型 Non blocking I O 应用程序轮询调用socket相关函数检查请求 不需
  • 利用CHAT写实验结论

    问CHAT 通过观察放置在玻璃表面上的单个水滴 人们可以观察到水滴充当成像系统 探究这样一个透镜的放大倍数和分辨率 CHAT回复 实验报告标题 利用玻璃表面的单一水滴观察成像系统的放大倍数和分辨率 一 实验目的 通过对比和测量 研究和探索玻
  • 扬帆证券:三只松鼠去年扣非净利预增超1.4倍

    在 高端性价比 战略驱动下 三只松鼠 300783 重拾增势 1月15日晚间 三只松鼠发布成绩预告 预计2023年度净赢利为2亿元至2 2亿元 同比增加54 97 至70 47 扣非后净赢利为1亿元至1 1亿元 同比增速达146 9 至17
  • 不要再苦苦寻觅了!AI 大模型面试指南(含答案)的最全总结来了!

    AI 大模型技术经过2023年的狂飙 2024年必将迎来应用的落地 对 IT 同学来讲 这里蕴含着大量的技术机会 越来越多的企业开始招聘 AI 大模型岗位 本文梳理了 AI 大模型开发技术的面试之道 从 AI 大模型基础面 AI 大模型进阶
  • AI帮助终结全球饥饿问题

    全球饥饿问题是牵动人心的头等大事 5月28日是 世界饥饿日 这一问题更值得关注 让人人都能吃饱的想法不仅令人向往 而且很快就会变成现实 与大多数新事物引进一样 对于在控制世界粮食供应这样复杂的任务中AI究竟应该发挥多大的作用 人们还踟蹰不前
  • 主流进销存系统有哪些?企业该如何选择进销存系统?

    主流进销存系统有哪些 企业该如何选择进销存系统 永久免费 的软件 这个可能还真不太可能有 而且就算有 也只能说是相对免费 因为要么就是数据存量有限 要么就是功能有限 数据 信息都不保障 并且功能不完全 免费 免费软件 免费进销存 诸如此类
  • 3D点云检测神技 | UFO来了!让PointPillars、PV-RCNN统统涨点!

    作者 AI驾驶员 编辑 智驾实验室 点击下方 卡片 关注 自动驾驶之心 公众号 ADAS巨卷干货 即可获取 点击进入 自动驾驶之心 3D目标检测 技术交流群 本文只做学术分享 如有侵权 联系删文 在这篇论文中提出了一个关于在3D点云中检测未
  • 基于节点电价的电网对电动汽车接纳能力评估模型研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码 数据
  • 深度学习(5)--Keras实战

    一 Keras基础概念 Keras是深度学习中的一个神经网络框架 是一个高级神经网络API 用Python编写 可以在TensorFlow CNTK或Theano之上运行 Keras优点 1 允许简单快速的原型设计 用户友好性 模块化和可扩

随机推荐

  • com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found

    fasterxml jackson 将对象转换为json报错处理 Spring Boot程序中 JsonIgnoreProperties与 JsonIgnore基本使用
  • python中的pd进行数据处理

    1 用pd读取excel中的的某一列生成列表 并将集合或列表写进excel def excel one line to list df pd read excel r poi grid xlsx usecols 1 names None 读
  • Robot Framework Selenium UI自动化测试 --- 进阶篇

    回顾 如果您对Robot Framework Selenium 以下简称RFS 没有基础概念和使用经验 请先阅读入门篇 入门篇对RFS有基础的介绍和使用教程 展望 本篇主要讲述了如何工程化的使用RFS 并穿插介绍各种常用关键字和使用技巧 希
  • vue3 数组清空与重新赋值

    vue3里面 如果数组是用reactive 声明的 要清空数组得用list length 0 如果想要使用list 或者直接赋值类型list 1 2 3 4 5 得把数组用ref 来声明 然后用list value 来修改 然后如果是对象里
  • 使用Stream流,根据条件去重并求数量

    一 去重类型 1 先创建数据 放入4个对象 其中有两个对象name属性相同 求name不一样的对象有多少数量 List
  • 锈湖新作地铁繁花试玩版正式上线啦

    地铁繁花是锈湖厂商新作点击式解谜冒险解谜游戏 英文名称为 Underground Blossom 在游戏中你将深入锈湖的地下 扮演并追溯Laura Vanderboom的人生和记忆吧 从一个车站到另一个车站 每个地铁站都象征着劳拉的一段过去
  • Ubuntu16.04下安装JDK1.8

    前提条件 拥有Ubuntu16 04环境 安装步骤 下载JDK安装包 下载版本 jdk 8u171 linux x64 tar gz 下载方式 云盘下载 云盘下载 提取码 7brp 官网下载 https www oracle com tec
  • python获取clickhouse数据表的全部列名称

    使用python获取的方法 import clickhouse connect client clickhouse connect get client host 127 0 0 1 def get col name table name
  • 详解:Char 和 varChar 之间的区别

    MySQL中的字符串有两个常用的类型 char和varchar 二者各有优势 下面我们来详细分析一下 通常在建表的时候对于String 类型的数据定义我们或许会很纠结 什么时候用char 什么时候用 varchar 呢 首先可以明确的是 c
  • 如何解决java.lang.NoClassDefFoundError--第二部分

    如何解决NoClassDefFoundError 第二部分 第一部分请看 http vipcowrie iteye com blog 1561291 本文面向的是JAVA初学者 建议你们自己编译和运行例子程序 本文包含了NoClassDef
  • c++单链表的简单实现(内含实现代码)

    考研报名等待之时闲来无事 写了一个简单的单链表 简单实现了以下功能 头插法建立单链表 按序遍历链表 单链表原地排序 不使用额外的空间 单链表按序删除 单链表原地倒置 附上代码如下 节点结构体定义 typedef int ElemType t
  • uniapp uview内置样式记录

    uview内置样式 文字省略 u line 1 u line 2 u line 3 u line 4 u line 5五个类名在文字超出内容盒子时 分别只显示一行 两行 三行 四行 五行 省略号 定位 uView内置了关于相对和绝对定位的两
  • 基于springboot的旅游信息管理系统完整源码

    基于springboot的旅游信息管理系统完整源码 技术栈 jdk1 8 mysql8 maven3 6 0 idea 功能模块 旅游路线 旅游景点 旅游酒店 旅游车票 旅游保险 旅游策略 订单管理 留言管理 数据分析等等 项目下载 htt
  • Matlab 如何生成一个[a,b]范围内随机整数的2种方法【已经解决】

    如何使用MATLAB生成一个 a b 范围内的随机整数 比如 随机生成 9 13 范围内的一个 或多个 整数 首先感谢 slandarer的指正 现已经更改 round 为四舍五入取整 而非向上取整 方法1为一个较为不错的方法 方法1 ra
  • 游戏开发-虚幻引擎天源了 [分享]

    https www unrealengine com zh CN 虚幻引擎4现在可供每个人免费使用 而且所有未来的更新都将免费 您可以下载引擎并将其用于游戏开发的各个方面 包括教育 建筑以及可视化 甚至虚拟现 实 电影和动画 当您发布游戏或
  • 计算机图形学入门(十六)-光线追踪(渲染方程)

    本部分主要介绍了渲染方程的逐步完善和简单的推导过程 从BRDF开始 到反射公式的推导再到渲染方程的完善 最后展示了实际渲染的例子 学习视频 GAMES101 现代计算机图形学入门 闫令琪 哔哩哔哩 bilibilihttps www bil
  • leaftlet 显示个性化图标、旋转图标

    1 引用leaftlet 高版本 比如1 8 3 在map js 中定义图标 L marker geo rotationAngle 270 icon L AwesomeMarkers icon icon awesomeIcon prefix
  • 创建匿名线程的5种方式

    package mythread 使用匿名内部类开启线程 public class Demo02anonymous thread public static void main String args 方式一 使用匿名内部类创建线程的子类对
  • java 是静态语言还是动态_java是动态语言还是静态语言?,

    java是动态语言还是静态语言 Java是动态语言还是静态语言 Java是一种静态语言 Java是编译时确定的变量类型 不能在运行时更改 在类型转换中也是强制的 例如 当大规模整数类型转换为小规模整数类型时 必须进行强转换 比如int必须强
  • 【TVM 学习资料】用 Schedule 模板和 AutoTVM 优化算子

    本篇文章译自英文文档 Optimizing Operators with Schedule Templates and AutoTVM 作者是 Lianmin Zheng Chris Hoge 更多 TVM 中文文档 访问 TVM 中文站