- 博客(51)
- 收藏
- 关注
翻译 oneAPI GPU 优化指南 - 预取
用户引导的数据预取是一种隐藏由于低级缓存未命中和主内存访问产生的延迟的有用技术。上述预取编译指示是 Intel® 扩展,并适用于 Intel® 数据中心 Max 系列 GPU 及以后的产品。编译指示的主要方面包括:: 使用可选的指定预取数据的目标。有效值为 0 (No-op), 2 (仅预取到 L2) 和 4(预取到 L1 和 L2)。如果未指定值,默认值为 0。: 使用 OpenMP 语法指定连续数组部分arrsect。例如,在 C 中使用 a[2:4],在 Fortran 中使用 a(2:4)。
2024-04-30 11:33:14
38
1
翻译 oneAPI GPU 优化指南 - 子句: is_device_ptr, use_device_ptr, has_device_addr, use_device_addr
如果一个列表项是一个指向映射到设备的对象的指针,那么在结构中对列表项的引用将被转换为对一个设备指针的引用, 该设备指针是局部于结构体并且引用相应对象的设备地址。如果列表项不指向映射对象,它必须包含一个有效的设备地址,列表项引用将被转换为对一个局部设备指针的引用,该指针引用这个设备地址。在 C++ 中,每个列表项应为指针类型、数组类型、指针的引用类型或数组的引用类型。在 C++ 中,每个列表项应为指针类型、数组类型、指针的引用类型或数组的引用类型。它表示列表项已经具有有效的设备地址,因此可以直接从设备访问。
2024-04-30 11:29:51
26
翻译 oneAPI GPU 优化指南 - Fortran 示例
我们将使用一个 Fortran 示例来说明如何适当地选择内存分配,以避免冗余的数据传输并提高性能。这个 Fortran 示例使用了 OpenMP 部署,并且是根据 NWChem (一个高性能的计算化学应用程序)进行改编的。在这个示例中,有一个 4 层的循环嵌套。最内层的 k 循环调用了omp_fbody例程,该例程将两个对sgemm的调用部署到设备上, 然后将一个 reduction 计算( reduction 变量emp4i和emp5i)部署到设备上。
2024-04-30 11:25:13
60
翻译 oneAPI GPU 优化指南 - 内存分配
本章节翻译by chenchensmail@163.com 原文:本节将探讨各种分配内存的方式,以及支持的分配类型。主机上的指针与设备上的指针大小相同。由主机拥有,预计将从系统内存中分配。主机分配可以由主机和所有支持的设备访问。因此,可以在主机和所有支持的设备上使用指向主机分配的相同指针。预计主机分配不会在系统内存和设备本地内存之间迁移。当在设备上访问指向主机分配的指针时, 数据通常会通过连接设备和主机的总线(如 PCI-Express )发送。由特定设备拥有,预计将从设备本地内存中分配。
2024-04-30 11:20:56
28
翻译 oneAPI GPU 优化指南 - 更好地使用 OpenMP 构造
区域完成,可以继续处理工作共享循环。通过工作共享循环末尾的隐式 barrier 保证了。区域(第 48 行)中计算两个向量 v1 和 v2 的乘积。一半的计算在主机上由执行。区域的末尾有一个隐式 barrier ,该 barrier 确保遇到。设备上进行另一半的计算。子句,那么性能可能会得到提高, 因为这样主线程就不必等待。区域完成后才能继续并参与主机上的计算(工作共享循环)。区域在设备上异步执行, 而无需主机线程空闲等待。构造的主机线程也可以继续。区域来在设备上进行计算。区域完成之前不能继续。
2024-04-30 11:12:00
28
翻译 oneAPI GPU 优化指南 - 使用更多的 GPU 资源
可以通过使用更多可以并行运行的 work-item 来提高部署代码的性能,从而利用更多的 GPU 资源(填满 GPU )。循环迭代的 ND-range 划分由编译器和 runtime 启发式算法决定,还取决于 GPU 驱动程序和硬件配置。因此它会随着时间而改变。但是,基于 LIBOMPTARGET_DEBUG=1 输出确定划分的方法将保持不变。
2024-04-30 10:44:43
93
翻译 oneAPI GPU 优化指南 - OpenMP 部署最佳实践
支持将 OpenMP 部署到 GPU 上。当使用 OpenMP 时,程序员在代码中插入设备指令, 指示编译器将应用程序的某些部分部署到 GPU 上。部署计算密集型代码可以获得更好的性能。本节涵盖了与 OpenMP 部署相关的各种主题, 以及如何提高部署代码的性能。基于 LLVM 的 Intel® C/C++ 和 Fortran 编译器,
2024-04-28 22:09:36
63
翻译 oneAPI GPU 优化指南 - OpenMP 部署调优指南 - 分析 OpenMP 应用程序性能的工具
它有助于识别应用程序中最耗时(热点)的函数,应用程序是 CPU 还是 GPU 限制, 它如何有效地将代码卸载到 GPU ,以及最佳的代码部分来优化顺序性能和线程性能等。有关此工具的信息,请参阅本文档的。选项时,查看跟踪末尾的主机和设备端摘要, 在 “API Timing Results” 和 “Device Timing Results” 标题下。文件头提供有关 SIMD 宽度,编译器选项以及其他信息的信息。的文件,该文件提供有关 GPU 的信息, 例如 EU 计数,线程计数,切片计数等。
2024-04-18 23:28:58
41
翻译 oneAPI GPU 优化指南 - OpenMP 部署调优指南 - 部署 oneMKL 计算到 GPU
Compile:Run:在特定 GPU 上的性能(无填充)如下(仅 1 堆栈):= 0。
2024-04-18 23:27:05
123
翻译 oneAPI GPU 优化指南 - OpenMP 部署调优指南 - 编译并运行 OpenMP 应用程序
使用以下编译器选项启用 OpenMP 部署到 Intel® GPU。这些选项适用于 C/C++ 和 Fortran。默认情况下,Intel® 编译器将程序转换为中间语言表示 SPIR-V, 并将其存储在编译过程生成的二进制文件中。该代码可以通过在 runtime 中将 SPIR-V 代码 转换为平台的汇编代码以便在任何硬件平台上运行。这个过程被称为即时 (JIT) 编译。要启用编译器优化报告的输出,请添加以下选项:-qopenmp编译器选项相当于-fiopenmp,并且 这两个选项可以互换使用。
2024-04-18 23:14:26
76
翻译 oneAPI GPU 优化指南 - OpenMP 部署调优指南 - 术语
如 “ SYCL 线程层次结构和映射” 一章所述, 部署到 GPU 上的并行循环(执行范围) 的迭代被划分为 work-group、sub-group 和 work-item。ND-range 表示总执行范围,它被划分为大小相等的 work-group。一个工 work-group 是一个 1、2 或 3 维的 work-item 集合。每个 work-group 可以划分为 sub-group。sub-group 表示一小段连续的 work-item, 它们被一起作为 SIMD 向量处理。
2024-04-18 23:12:29
15
翻译 oneAPI GPU 优化指南 - OpenMP 部署调优指南 - OpenMP 执行模式
在 Intel® 数据中心 GPU Max 系列上执行时, 整个 GPU (可能具有多个堆栈) 可以被视为一个设备, 或者每个堆栈都可以被视为一个设备。OpenMP 执行模式具有一个主机设备, 但有多个目标设备。设备是具有自己的 本地存储和数据环境的逻辑执行引擎。子句、 (隐式或显式) barrier 或其他同步机制 实现异步执行的代码区域之间的同步。子句指定), 并将构造中的代码部署到设备上。区域结束时, 数据从设备传输回主机(如果指定)。构造时,数据从主机传输到设备 (例如,如果由。
2024-04-18 23:10:46
34
原创 oneAPI GPU 优化指南 - OpenMP 部署调优指南 - OpenMP 指令
指令指定变量、函数 (C、C++ 和 Fortran )和子程序( Fortran )映射到设备。支持各种 OpenMP 指令,用于控制计算的部署 和数据映射到设备。指令根据指定的 motion 子句 使设备上变量的值与其原始主机变量一致。构造将循环的迭代分配给各个团队的主线程, 因此每个主线程执行迭代的子集。构造指定代码的特定部分在设备上执行, 以及如何将数据映射到设备。子句确定原始主机变量如何映射 到设备上的相应变量。设备上的变量值从设备复制到原始主机变量。原始主机变量的值被复制到设备上。
2024-04-18 23:09:05
402
翻译 oneAPI GPU 优化指南 - OpenMP 部署调优指南
支持将 OpenMP 部署到 GPU 上。当使用 OpenMP 时,程序员在代码中插入设备指令, 指示编译器将应用程序的某些部分部署到 GPU 上。部署计算密集型代码可以获得更好的性能。本节涵盖了与 OpenMP 部署相关的各种主题, 以及如何提高部署代码的性能。基于 LLVM 的 Intel® C/C++ 和 Fortran 编译器,
2024-04-18 23:06:43
31
翻译 oneAPI GPU 优化指南 - 访问buffer的不同模式
还有另一种编写kernel的方法,在设备上访问主机上的只读变量的副本,将其作为定义kernel的 lambda 函数中变量捕获的一部分, 如下所示。如果在此程序块内提交读取 A 或 B buffer的另一个kernel, 则在第二个kernel完成之前,该新kernel无法启动。访问模式通知runtime,在kernel开始执行之前,数据需要在设备上可用,但在计算结束时不需要将数据从设备复制到主机。进行的, 第44和45行中的声明使它们成为只读的,这可以防止它们在生命周期结束时从设备复制回主机。
2024-02-19 23:19:48
49
翻译 oneAPI GPU 优化指南 - 应避免在循环内声明buffer
在以下示例中,kernel在 for 循环内重复启动。由于buffer C 是在 for 循环内声明的, 因此它在每次循环迭代中都会被分配和释放。除了buffer的分配和释放之外,与buffer关联的内存在每次迭代中从主机重复 地传输到设备以及从设备传输到主机。当在 for 循环内重复启动kernel时,可以通过在循环外部声明buffer来防止重复分配和释放buffer。更好的方法是在 for 循环之前声明buffer C,以便仅分配和释放一次,从而通过避免主机和设备之间的多余数据传输来提高性能。
2024-02-19 23:13:30
55
翻译 oneAPI GPU 优化指南 - 避免在主机和设备之间来回移动数据
然后,通过对每个值应用函数,将这个新vector转换为第三个vector, 最后将这个第三个vector作为输入传到另一个kernel中以进行一些额外的计算。在许多领域中这种形式的计算非常常见,在这些领域中,算法是迭代的,并且一个计算的输出需要作为另一个计算的输入。在某些情况下,可能需要将加速器上的kernel计算的数据带到主机并对其进行一些操作, 然后将其发送回设备以进行进一步处理。在这种情况下,我们最终将产生设备到主机传输的开销,然后再次产生主机到设备传输的开销。的计算中,如下所示。
2024-02-19 23:12:12
61
翻译 oneAPI GPU 优化指南 - 优化主机和加速器之间的内存移动
如果buffer指向的内存未在page边界上对齐,则将分配新内存以便在page边界上对齐,并将buffer内容复制到该内存中。然后通过将buffer中的新内存与加速器共享(对于不共享任何内存的加速器,从主机上的buffer复制内容到设备上)或使用页表避免来复制可在设备上使用的内存(对于共享内存的加速器)。,则在创建buffer时它们将被复制并分配新内存,而不是重用主机vector中的内存。当执行此代码时,我们看到与传入vector相关联的地址与buffer中存在的内存以及加速器设备中存在的内存不同。
2024-02-19 23:10:15
59
翻译 oneAPI GPU 优化指南 - USM和buffer对性能影响
SYCL为设备上的内存管理提供了几种选择。本节将简要介绍相关概念以及性能折衷。有关深入解释,请参见。与其他语言特性一样,规范定义了行为但不定义实现,因此性能特征可能在软件版本和设备之间发生变化。本指南提供最佳的实践指导。Buffers。buffer是一个可以从设备和主机访问的数据容器。SYCL runtime通过提供分配、读取和写入内存的API来管理内存。runtime负责在主机和设备之间移动数据,并同步对数据的访问。(USM)。
2024-01-19 10:21:40
81
翻译 oneAPI GPU 优化指南 - 分配USM(Unified Shared Memory)
本章节翻译by weavingtime@formail.com 原文USM(Unified Shared Memory)允许程序使用 C/C++ 指针进行内存访问。SYCL中有三种分配内存的方式:分配的内存只能由指定设备访问,而不能由上下文中的其他设备或主机访问。数据始终保留在设备上,因此是kernel执行最快的选择。需要显式复制才能将数据传输到上下文中的主机或其他设备。主机和上下文中的任何其他设备都可以访问分配的内存。数据始终保留在主机上,并通过 PCI 从设备进行访问。
2024-01-19 10:18:13
123
翻译 oneAPI GPU 优化指南 -主机/设备内存, buffer 和 USM
加速器可以访问丰富的内存层级结构。利用层级结构中的正确级别对于获得最佳性能至关重要。本章节翻译by weavingtime@formail.com 原文。在本节中,我们将介绍如何对不同种类内存进行分配、移动和访问。
2024-01-19 10:14:40
42
翻译 oneAPI GPU 优化指南 - 使用 oneMKL 高效实现傅里叶相关
实际信号包含更多数据(和噪声), 但是无论您是对齐 1D 信号,叠加 2D 图像 还是执行 3D 体积图像配准,原则都是相同的。不幸的是, 添加人工信号的代码在主机上运行, 因此 SYCL runtime 必须使主机和设备数据一致。傅里叶相关中使用的信号通常很大, 特别是在 3D 成像应用中, 因此不必要的数据传输会降低性能。请注意,查找最大相关值位置 的最后一步在主机上执行。通常会向信号添加随机噪声,以防止 神经网络训练期间过度拟合, 向图像添加视觉效果,或改善从次优探测器 获得的信号的可检测性等。
2024-01-05 16:26:14
103
翻译 oneAPI GPU 优化指南 - 在 SYCL kernel 中使用标准库函数
oneDPL 和 oneMKL 示例执行相同的操作序列: 从时钟获取随机数种子,初始化随机数引擎, 选择所需的随机数分布,然后生成随机数。幸运的是, SYCL 库包含许多标准 C++ 函数 的替代方法,包括生成随机数的函数。的第18章(库)以了解支持的函数概述。这里提供了一个简单的示例, 以说明从 SYCL kernel 中 调用不受支持的函数时会发生什么。一些标准的 C++ 函数可以在 SYCL kernel 中调用,但并非所有函数都可以。, 但在 GPU 设备上没有 SYCL 等效函数。
2024-01-05 16:24:36
112
翻译 oneAPI GPU 优化指南 - 使用性能库
常见的计算密集型操作,如矩阵乘法, 已经得到了广泛的研究。他们还使用调优技术,如缓存阻塞和循环展开, 以在不管矩阵A和B的形状如何的情况下实现性能提升。本节讨论使用来自库的高效函数, 例如|onemkl|或|onednn|,而不是手工编写的替代方案。例如,矩阵乘法是一种常见的、直接的数学运算。用于数学运算的 oneMKL 用于数据分析和机器学习的 oneDAL 用于深度学习框架开发的 oneDNN 用于视频处理的 oneVPL 在创建自己的实现之前, 请检查所需操作是否已在oneAPI库中可用。
2024-01-05 16:22:27
188
翻译 oneAPI GPU 优化指南 - 使用库进行 GPU 部署
oneAPI 工具包提供了几个库, 可以通过提供专门的 API 简化编程过程, 用于优化应用程序。本节提供了使用库的步骤, 包括代码示例,用于应用程序加速。有关每个库的详细信息,包括可用的 API, 请参阅特定库的主要文档。
2024-01-04 16:47:23
108
翻译 oneAPI GPU 优化指南 - 在 kernel 中进行 I/O
在这些情况下,不能保证打印到控制台的语句的特定顺序,您应该预料到来自不同 work-item 的语句被交错输出。SYCL* 提供了一些功能,以帮助使这项任务类似于标准的 I/O C/C++ 程序, 但是由于加速器的工作方式,您需要理解一些特性。类,让您可以从 kernel 内部向控制台打印信息, 也提供了一种简单的方式来调试简单问题,而无需求助于调试器。如果大小不足, 可能会导致语句不被打印,或者打印的信息比预期的少。当再次运行此程序时,我们可能会以完全不同的顺序得到输出,这取决于线程的执行顺序。
2023-12-22 12:17:12
68
翻译 oneAPI GPU 优化指南 - 使用 SYCL Joint Matrix 扩展在 Intel® XMX 上编程
这统一了用于CPU 的 Intel® Advanced Matrix Extensions (Intel® AMX), 用于 GPU 的 Intel® Xe Matrix Extensions (Intel® XMX), 以及 NVIDIA* Tensor Cores 等目标。然而,对于想要构建自己的神经网络应用程序的用户来说,这些库和框架太上层,因为用户无法进行自定义优化, 并且很臃肿,因为库的体积很大。此外,机器学习领域引入了新的操作并持续变化,对于这些操作, 框架和库没有提供及时和高效的解决方案。
2023-12-22 12:01:57
146
翻译 oneAPI GPU 优化指南 - 避免冗余的 queue 构建
context 是由用户直接构建的,或者在创建 queue 时隐式构建的, 用于保存 SYCL runtime 和 SYCL 后端操作设备所需的所有 runtime 信息。一般来说,创建一个新的 context 是一项繁重的操作, 因为每次向新 context 的 queue 提交 kernel 时,都需要 JIT 编译程序。上面的 kernel 被重写为下面的样子,其中在循环内部创建的新 queue 和循环外部的 queue 共享 context。在这种情况下,性能与一个 queue 的性能相同。
2023-12-22 11:45:16
63
翻译 oneAPI GPU 优化指南 - 向多个 queue 提交 kernel
这将防止需要传输输入 buffer,但是 kernel 的内存占用将会增加, 因为所有的输出 buffer 必须同时驻留在 context 中,而在之前使用相同 queue 的方法中, 设备上的相同内存可以用于输出 buffer。在上面的示例中,kernel 的计算需求较低,所以整体执行时间主要由内存传输主导。需要注意的问题是,当一个 kernel 被提交到具有不同 context 的新 queue 时, JIT 过程会将 kernel 编译到与 context 关联的新设备。
2023-12-22 11:36:43
62
翻译 oneAPI GPU 优化指南 - 在设备端同时执行多个 kernel
在底层 queue 是按顺序的情况下,这些 kernel 不能并行执行,即使机器中有足够的资源, 且 kernel 之间没有依赖关系,它们也必须顺序执行。在 kernel 的扩展性不强,因此无法有效利用全机器计算资源的情况下, 最好只通过适当选择 work-group/work-item 值来分配所需的计算单元,并尝试同时执行多个 kernel。当 queue 是无序的时候,总的执行时间会大大降低,这表明机器能够同时执行来自 queue 的不同的 kernel。
2023-12-15 12:08:10
83
翻译 oneAPI GPU 优化指南 - Kernel 启动
这是由主机端提交的 kernel 在设备端开始执行的时间。由主机端发起的 kernel 提交和实际开始执行并不立即发生 - 它们是异步的, 因此我们必须跟踪与 kernel 相关的以下时间。这是因为在主机上完成 kernel 提交后, 设备端需要一些时间来准备执行 kernel,这取决于设备的可用性和 runtime 的调度策略。相同的代码,如果在 parallel_for 的末尾没有 wait(),那么它将测量主机端将 kernel 提交给 runtime 所需的时间。Kernel 提交的开始时间。
2023-12-15 11:52:01
60
翻译 oneAPI GPU 优化指南 - 归约运算
在下面显示的 kernel 中,work-group 中的每个 work-item 都参与归约网络, 最终将该 work-group 中的所有元素求和。在这个 kernel 中,每个 work-item 操作一定数量的向量元素, 然后 work-group 中的一个线程通过线性遍历包含中间结果的 SLM 将所有这些元素归约为一个结果。归约运算符是关联的,在某些情况下也是可交换的。下面的 kernel 与上面的类似,只是使用树形归约来减少 work-group 中所有 work-item 的中间结果。
2023-12-15 11:41:19
173
翻译 oneAPI GPU 优化指南 - 选择 work-group 大小的注意事项
在 SYCL 中,您可以为 nd_range kernel 选择 work-group 大小。work-group 的大小对计算资源,向量通道和 work-item 之间的通信的利用率具有重要影响。同一 work-group 中的 work-item 可能具有对 SLM 和硬件同步功能等硬件资源的访问权限, 这将使它们比跨 work-group 的 work-item 运行和通信更高效。因此, 通常应选择加速器支持的最大 work-group 大小。
2023-12-15 10:23:10
85
翻译 oneAPI GPU 优化指南 - 本地 barrier 与全局原子操作
这种累积是并行完成的,然后更新到 work-group 中所有 work-item 共享的数组中。此时,work-group 中的所有 work-item 使用 barrier 在彼此之间同步, 以在共享内存中将中间结果减少到最终结果。这个 kernel 显式地创建了一个 work-group, 并将向量中所有元素的责任分配给 work-group 中的 work-item。在下面显示的 kernel 中,使用全局原子操作解决了相同的问题, 其中每个 work-item 使用它需要累积的值更新全局变量。
2023-12-08 14:35:58
74
翻译 oneAPI GPU 优化指南 - 原子操作
原子操作允许多个 work-item 通过内存进行任何跨 work-item 通信。SYCL 原子类似于 C++ 原子,保证被原子保护的资源的访问作为单个单元执行。以下因素影响原子操作的性能和合法性:数据类型本地与全局地址空间主机端、共享和设备端分配的 USM。
2023-12-08 13:33:32
119
翻译 oneAPI GPU 优化指南 - 一个 kernel 中的线程同步
accessor 类指定 buffer 和图像数据结构的获取和释放。根据它们创建和销毁的位置, 运行时会生成适当的数据传输和同步的原生操作。kernel 中的 work-item 之间有多种方式可以同步以交换数据、更新数据或按特定顺序协作完成任务。函数,而不是通过在 work-group 中显式地使用 barrier 操作,使 work-group 内的同步变得明确。在许多情况下,任何上述同步事件的方式都可以用来实现相同的功能,但效率和性能方面存在显著差异。在描述计算的分层并行模型中,通过多个实例调用。
2023-12-08 13:15:33
87
翻译 oneAPI GPU 优化指南 - 指针别名和 Restrict 指令
在某些情况下, 获得更高性能的代码的更好方法是显式地指定 work-group 的数量和大小,向编译器提供提示以获得良好性能。这将允许编译器提升 load 和 store 次序,从而为指令完成提供更多时间并获得更好的指令调度。展开的优点是执行的指令更少,因为循环不必迭代那么多次,从而节省了比较和分支的指令。下面的 kernel 使用了 restrict 指令,它向编译器提供了一个提示,即循环内访问的向量之间没有别名, 编译器可以像在前面的示例中手动完成的那样,将 load 提升到 store 之前。
2023-12-08 12:51:25
44
翻译 oneAPI GPU 优化指南 - 共享本地内存(SLM)
通常,work-item 需要共享数据并相互通信。一方面,所有 work-group 中的所有 work-item 都可以访问全局内存, 因此数据共享和通信可以通过全局内存进行。但是,由于其较低的带宽和较高的延迟, 通过全局内存进行共享和通信效率较低。另一方面, 在矢量引擎(VE)线程中同时执行的 sub-group 中的 work-item 可以非常高效地共享数据并相互通信, 但 sub-group 中的 work-item 数量通常较小,数据共享和通信的范围非常有限。
2023-12-08 11:38:20
142
翻译 oneAPI GPU 优化指南 - 小寄存器模式与大寄存器模式
Intel® 数据中心 GPU Max 系列产品支持两种 GRF 模式:小 GRF 模式和大 GRF 模式。每个执行单元(EU)总共有 64 KB 的寄存器存储空间。在小 GRF 模式下, 一个 EU 中的单个硬件线程可以访问 128 个 GRF 寄存器,每个寄存器宽度为 64B。在这种模式下,每个 EU 可用的硬件线程数为 8。在大 GRF 模式下, 一个 EU 中的单个硬件线程可以访问 256 个 GRF 寄存器,每个寄存器宽度为 64B。在这种模式下,每个 EU 可用的硬件线程数为 4。
2023-11-24 11:09:08
116
1
翻译 oneAPI GPU 优化指南 - 寄存器化和避免寄存器溢出
然而,寄存器空间有限,比内存空间小得多。如果没有足够的寄存器来保存所有变量,则寄存器可能会溢出, 或者当前在寄存器中的某些变量可以移动到内存中以腾出空间给其他变量。如果同一 sub-group 中的 work-item 共享私有 bin, 那么整个 sub-group 只需要 256 个私有 bin,或者每个 work-item 只需要 16 个私有 bin。如果 bin 的数量很大,例如大于 1024,即使私有 bin 在同一 sub-group 中共享, 也不会有足够的寄存器空间用于存储私有 bin。
2023-11-24 10:38:45
140
1
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人