最新资讯
dev.to 在信任 AI 生成的测试用例前,如何逐条复核 unite.ai Franklin Templeton 构建 AI 研究工作流以揭示风险 techxplore.com AI 助力寻找更优的钛合金 3D 打印工艺参数 engadget.com Perplexity 推出“teammates”日常工作任务 AI agent computerworld.com 别提数据中心:OpenAI 改称“compute campuses” theguardian.com 俄美试图阻止就自主武器制定具有约束力的规则 bloomberg.com Nvidia 加强东南亚芯片追踪以打击走私 thestack.technology 模型路由需求升温,Agent Router 推出开源替代方案 www.youtube.com Google DeepMind 的 Philipp Schmid:AI 同事需要具备的七个要素 unite.ai Cashfree 为 AI Agent 的托管结账加入 WebMCP 工具 dev.to 我们在 DolphinBench 上测试记忆检索:纯关键词搜索暴露了自身回归 dev.to 别再把 Agent 写成 while 循环,把它建模为状态机
多元动态
登录 注册
开源工具 www.infoq.com · @Onur Satici

Vortex:从S3到GPU的一次拷贝,革新ML训练数据加载

原文 我的收藏

InfoQ演讲中,Onur Satici介绍了Linux基金会下的开源列式文件格式Vortex,通过级联轻量编码、基于布局的段剪枝和零拷贝内存管道,消除CPU/NVMe瓶颈,实现高达60 Gbps的S3到GPU流式传输,无需预先数据重处理。

AI推荐理由 介绍Vortex开源列式文件格式,实现从S3到GPU的高效数据加载,速度达60 Gbps,无需预处理。技术细节明确,对ML训练数据管道优化有实际价值,来源InfoQ专业。

85

正文

InfoQ首页 演讲 从S3到GPU一次拷贝:重新思考ML训练的数据加载 AI、ML与数据工程 从S3到GPU一次拷贝:重新思考ML训练的数据加载 查看演讲 垂直 水平 全屏 速度: 1倍 1.25倍 1.5倍 2倍 下载 幻灯片 49:37 摘要 Onur Satici解释了Vortex(Linux基金会下的开源列式文件格式)如何革新高吞吐量数据加载。他详细介绍了级联轻量级编码、基于布局的段剪枝和零拷贝内存管道如何消除CPU/NVMe瓶颈,将S3数据直接流式传输到GPU,速度高达60 Gbps,而无需预先进行数据重新处理。 简介 Onur Satici是SpiralDB的一名高级工程师,也是Vortex(现为Linux基金会(LF AI & Data)一部分的开源列式文件格式)的核心维护者。他专注于高性能数据系统、GPU加速,并致力于在堆栈的每一层加速分析工作负载。 关于会议 软件正在改变世界。QCon London通过促进开发者社区中知识和创新的传播来赋能软件开发。QCon是一场由实践者驱动的会议,专为技术团队负责人、架构师、工程总监和项目经理设计,他们影响着团队的创新。 文字记录 Onur Satici:在我讲任何内容之前,我想向你们展示这个视频。不是特别关注视频中播放的内容,而是我们如何制作这个视频。因为这不是由某个主磁盘上的常规MP4文件支持的,而是文件扫描的可视化。我的意思是,我们有一个Vortex文件,Vortex是一种列式文件格式。它有三列,每列对应一种颜色。每列然后为这个视频的每一帧有一行。每帧包含大约800万像素,因为这是一个4K视频。然后我们处理这些数据,即三列,每帧约800万像素,每秒60帧,因此从S3通过网卡、经过CPU、到达GPU,大约每秒13吉比特。GPU以这种速度看到这些数据,但我们想看看GPU中发生了什么,我们想将其可视化。 我们无法将那么多数据下载到笔记本电脑上,因为它已经在GPU中,所以我使用GPU本身将其重新编码为H.264,包装成TCP缓冲区,通过我的SSH隧道发送,我们可以显示并查看正在发生的事情。现在看看这个。这是同一个视频,但这没有经过任何后处理。这实际上是列剪枝或投影剪枝的可视化,其中我们只在投影表达式中选择一列,并且只有与该列关联的字节通过相同的管道流动,保持相同的带宽,然后最终得到你之前看到的视频。 今天我想谈谈这是如何可能的,以及使我们能够做到这一点的技术。我是Onur。我在SpiralDB工作。我帮助维护Vortex,这是一个在Linux基金会下的开源文件格式。我想谈谈是什么让Vortex特别。我将从为什么我们首先需要这种速度开始。然后我将转向Vortex本身,解释Vortex的一些主要主题,布局和数组以及扫描本身,最后以什么是可能的结束。 GPU数据加载中的问题 从问题开始,为什么我们需要速度。因为你有一些你雇用的GPU,你想使用它们。GPU很昂贵,每次它们闲置时,都很昂贵,并且是不做有价值工作的机会成本。我想说我们在利用GPU时支付两种税。第一种是移动税。将数据从你拥有的某个源获取到GPU并非易事,而且并不便宜。如果你使用web数据集进行训练,或者例如MosaicML的流式数据集,那么它们或多或少会经历这些步骤。例如,假设你的数据在S3中。你从S3获取到磁盘,到NVMe。从NVMe,你将压缩字节读入RAM。你在CPU上解压缩,然后通过主机到设备链接PCIe将这些解压缩的字节发送到GPU。 这不仅有许多你必须遵循的步骤。如果你放大每个连接支持带宽,你可以看到我们主要受限于CPU解压缩和NVMe吞吐量。它们都持久化到NVMe的原因是此管道的主要消费者是PyTorch。PyTorch是Python。Python不喜欢多线程,因此我们使用多进程。每个工作进程不想踩到彼此的脚趾,因此它们想要去重下载,所以它们将分片持久化在磁盘上。理想情况下,我们希望此管道看起来更像那样,从S3获取,消除通过网卡速度和PCIe速度到GPU的带宽瓶颈。然后,进一步,你想使用正确的压缩,使你能够在GPU中解压缩,并行压缩,GPU解压缩比CPU快得多。你不仅消除了NVMe中的瓶颈,而且现在流式传输更快,你可能以每秒50吉比特的速度流式传输,但这些字节是压缩的。如果你选择正确的压缩,实际上,此管道的运行速度比实际压缩速度快10倍。那是移动。 我想谈的另一种税是决策。这是当你想迭代训练工作负载时。你已经从源加载到GPU,也许你对训练做了一些预处理,然后你想评估结果。然后你想尝试的,例如,是新的数据混合,或者你想改变课程。你想对这个数据应用一些新过滤器。使用我们当前的工具,你必须重新处理整个数据,可能重新标记化,重新洗牌以获得正确的顺序,然后再次加载到你的源,可能是S3,可能是其他地方,但这很慢。如果你看一下你花费时间百分比的假设饼图,你想最大化灰色(你在思考)和绿色(你的GPU在工作)。其他一切都是开销。你想把它变成那样,从源加载到GPU而不浪费时间。然后一旦你评估并决定你想改变输入源中的某些内容,你只需改变你的扫描。你只需给出不同的查询,不同的过滤器。你不需要重新处理。然后你可以立即开始下一次迭代的训练。 Vortex - 概述 Vortex如何使用不同的文件格式实现这一切?Vortex,快速概述它是什么,它是一种列式文件格式。它类似于Parquet,但也与Parquet有很大不同。它有一个可扩展的规范,一个最小的规范,你可以插入到各个点。它分离了逻辑类型与物理类型。这是与Parquet的主要区别之一,Vortex没有将类型本身(如整数)与它们在磁盘上的编码方式联系起来的观念。由你决定,它们是解耦的。它尽可能少做,尽可能晚做,节省大量用于物化中间结果的内存拷贝,并利用查询优化。然后它可以将大部分投影、过滤和聚合从你的计算引擎推入文件格式本身。此外,它进行压缩的方式也不同。 它不使用会丢失底层数据含义的块压缩。它确实使用轻量级和级联编码,这些编码也可以提供类似的压缩比,但更重要的是,它们允许你对压缩数据进行计算。你可以进行随机访问,你可以进行聚合。其中一些计算甚至可能比在解压缩数据上进行计算更快。为了给出一些具体数字与Parquet相比,我们展示的S3到GPU扫描大约快30倍。得益于Vortex的编码和压缩,它在随机访问方面比Parquet快约100倍或更多。 文件是什么样的?我们将从文件读取器的角度来处理这个问题。有一个固定大小的Postscript,你读取它,它有许多指向文件其他部分的指针。有一个页脚。页脚本身是一个图例,是你期望在此文件中看到的内容的物料清单。你有一个数组列表和一个布局列表。数组是你在Vortex中编码数据的方式,布局类似于逻辑计划。它们是I/O层的剪枝。我们稍后将深入探讨。这些向你展示了主要的插件点。如果你有一个新数组或新布局,你只需在此处添加它,并且你在读取器中有逻辑,然后Vortex将委托给你的读取器来做你想做的事情。第三,也许是最重要的,是你期望在此文件中看到的段列表,段是文件的大部分。 它们本身有数组数据。它们带有三元组,即偏移量、长度和对齐。它们是指针,但它们也有对齐。对齐很重要,我们认为,要烘烤在文件中,因为如果你有对齐,你知道如果你想在加载时使用SIMD操作或GPU内核,如果你事先知道对齐,在你的接收缓冲区中,你以正确的对齐分配该缓冲区,因此你不需要复制,只需在其上调度计算。我已经谈到了数组和布局,但什么是布局?布局使你能够根据过滤器和投影尽快剪枝这些段。它们类似于逻辑计划。它们也非常最小。这是整个扁平缓冲区定义。我想指出的可能只有两个重要点是最后两个字段。它有子节点,这是一个自身的数组,所以它是一棵树,树中的每个节点都有段。 段是一个整数列表,这些整数指向段的偏移量。我们从页脚知道它们在指针方面的位置,比如精确的字节范围。布局中的每个节点告诉我,我负责读取这些段,给我一个过滤器,我会告诉你我负责的这些段中哪些你必须读取。 Vortex布局 为了举例说明布局如何工作,让我们从一个模式开始,例如。我们有根布局。它有两列,名称(字符串),年龄(整数)。StructLayout只告诉它,我每列有一个子节点,所以你期望它有两个不同的子节点,每列一个。每个子节点本身是一个ZonedLayout。ZonedLayout很有趣,因为它不仅包含作为段的数据,还包含另一个段,即你期望读取的数据的摘要统计。那也是一个段。当你读取该段时,它是一个区域映射。区域映射本身是一个Vortex数组,但它确实有实际数据中的每行范围,并有统计列,如最小值、最大值。数据然后只是多个段的串联。这是一种在你想读取时将数据分解成更小数组的方法。 那么这是如何工作的?假设你有这个查询。你想获取名称列,但你只想获取年龄列大于30的行。你从根开始。根知道它在每列中有投影和过滤器,因此它通过这个委托给每个ZonedLayout。年龄将收到一个过滤器,然后它将首先加载区域映射,检查区域映射,检查过滤器,找出此数据中哪些段通过过滤器。假设最后一段通过过滤器,那么它返回最后一段。我们还让名称列返回最后一段,因为那是我们需要的实际数据。对实际行进行实际过滤,并返回我们需要的行。通过读取区域映射并且只读取30%的段 到你的源,可能是S3,可能是其他地方,但这很慢。如果你看一下你花费时间百分比的假设饼图,你想最大化灰色(你在思考)和绿色(你的GPU在工作)。其他一切都是开销。你想把它变成那样,从源加载到GPU而不浪费时间。然后一旦你评估并决定你想改变输入源中的某些内容,你只需改变你的扫描。你只需给出不同的查询,不同的过滤器。你不需要重新处理。然后你可以立即开始下一次迭代的训练。 Vortex - 概述 Vortex如何使用不同的文件格式实现这一切?Vortex,快速概述它是什么,它是一种列式文件格式。它类似于Parquet,但也与Parquet有很大不同。它有一个可扩展的规范,一个最小的规范,你可以插入到各个点。它分离了逻辑类型与物理类型。这是与Parquet的主要区别之一,Vortex没有将类型本身(如整数)与它们在磁盘上的编码方式联系起来的观念。由你决定,它们是解耦的。它尽可能少做,尽可能晚做,节省大量用于物化中间结果的内存拷贝,并利用查询优化。然后它可以将大部分投影、过滤和聚合从你的计算引擎推入文件格式本身。此外,它进行压缩的方式也不同。 它不使用会丢失底层数据含义的块压缩。它确实使用轻量级和级联编码,这些编码也可以提供类似的压缩比,但更重要的是,它们允许你对压缩数据进行计算。你可以进行随机访问,你可以进行聚合。其中一些计算甚至可能比在解压缩数据上进行计算更快。为了给出一些具体数字与Parquet相比,我们展示的S3到GPU扫描大约快30倍。得益于Vortex的编码和压缩,它在随机访问方面比Parquet快约100倍或更多。 文件是什么样的?我们将从文件读取器的角度来处理这个问题。有一个固定大小的Postscript,你读取它,它有许多指向文件其他部分的指针。有一个页脚。页脚本身是一个图例,是你期望在此文件中看到的内容的物料清单。你有一个数组列表和一个布局列表。数组是你在Vortex中编码数据的方式,布局类似于逻辑计划。它们是I/O层的剪枝。我们稍后将深入探讨。这些向你展示了主要的插件点。如果你有一个新数组或新布局,你只需在此处添加它,并且你在读取器中有逻辑,然后Vortex将委托给你的读取器来做你想做的事情。第三,也许是最重要的,是你期望在此文件中看到的段列表,段是文件的大部分。 它们本身有数组数据。它们带有三元组,即偏移量、长度和对齐。它们是指针,但它们也有对齐。对齐很重要,我们认为,要烘烤在文件中,因为如果你有对齐,你知道如果你想在加载时使用SIMD操作或GPU内核,如果你事先知道对齐,在你的接收缓冲区中,你以正确的对齐分配该缓冲区,因此你不需要复制,只需在其上调度计算。我已经谈到了数组和布局,但什么是布局?布局使你能够根据过滤器和投影尽快剪枝这些段。它们类似于逻辑计划。它们也非常最小。这是整个扁平缓冲区定义。我想指出的可能只有两个重要点是最后两个字段。它有子节点,这是一个自身的数组,所以它是一棵树,树中的每个节点都有段。 段是一个整数列表,这些整数指向段的偏移量。我们从页脚知道它们在指针方面的位置,比如精确的字节范围。布局中的每个节点告诉我,我负责读取这些段,给我一个过滤器,我会告诉你我负责的这些段中哪些你必须读取。 Vortex布局 为了举例说明布局如何工作,让我们从一个模式开始,例如。我们有根布局。它有两列,名称(字符串),年龄(整数)。StructLayout只告诉它,我每列有一个子节点,所以你期望它有两个不同的子节点,每列一个。每个子节点本身是一个ZonedLayout。ZonedLayout很有趣,因为它不仅包含作为段的数据,还包含另一个段,即你期望读取的数据的摘要统计。那也是一个段。当你读取该段时,它是一个区域映射。区域映射本身是一个Vortex数组,但它确实有实际数据中的每行范围,并有统计列,如最小值、最大值。数据然后只是多个段的串联。这是一种在你想读取时将数据分解成更小数组的方法。 那么这是如何工作的?假设你有这个查询。你想获取名称列,但你只想获取年龄列大于30的行。你从根开始。根知道它在每列中有投影和过滤器,因此它通过这个委托给每个ZonedLayout。年龄将收到一个过滤器,然后它将首先加载区域映射,检查区域映射,检查过滤器,找出此数据中哪些段通过过滤器。假设最后一段通过过滤器,那么它返回最后一段。我们还让名称列返回最后一段,因为那是我们需要的实际数据。对实际行进行实际过滤,并返回我们需要的行。通过读取区域映射并且只读取30%的段 件,你可以满足这个查询。 也许这是一个更详细的视图。中心是区域映射。那是一个段。我们读取它。我们反序列化它。它是一个数组。每个行范围有一行,我们有过滤器。通过只看最大值,我们可以证明只有这里的最后一行可以有匹配此过滤器的行。所有其他的你都不需要。你读取整个块。这是Vortex的原子读取单元。里面是一个段。然后,Vortex的巧妙之处在于我们的压缩是可切片的。你不需要解压所有内容。你先切片,然后只解压匹配我们行范围的行。最后8k行被解压。这就像尽可能少做,尽可能晚做。块粒度和行粒度之间的区别,我认为也很重要,因为它们服务于不同的目的。 块粒度是一个I/O关注点。它处理你有什么类型的I/O源,带宽和延迟特性是什么,所以你可以在Vortex中调整它们。区域粒度本身是一个计算关注点,因为根据你的架构,你可能有不同的更宽的SIMD寄存器想使用,这些与你拥有的不同数据长度配合得很好。你可以根据自己的喜好调整它们。 Vortex数组 我们讨论了读取段和布局帮助我们修剪到我们想要的段,但每个段包含什么?它们包含一个序列化的、很可能压缩的Vortex数组。一个Vortex数组,我也想用一个例子来解释。假设我们有这个数组。它是一个数组的数组,所以元素本身是一个列表。内部列表中的元素是字符串。我们想对它们进行编码和压缩,同时保持含义。我们可以从一个类似于Arrow的ListArray开始。它基本上将值展平,并将展平的值保存为值子节点,然后它保留内部大括号位置,因为展平时会移除它们,丢失信息,所以它将信息保留在偏移子节点中。任何两个连续的偏移将指向内部列表位置的内部大括号。 你可以看到这也是一个树。现在我们有了ListArray,但两个子节点本身也是数组。我们可以继续,进一步压缩。例如,值子节点只有三个唯一元素,所以我们字典编码它们。字典编码获取唯一值并将它们存储为子节点,所以就像值子节点中一个城市只出现一次,但你然后将原始数组的重复编码为指向值子节点的索引。然后你将值本身和重复解耦为单独的子节点。然后你可以继续,因为代码现在有连续的重复值,称为游程。你可以游程编码它们,或者在Vortex中,更具体地说,游程结束编码它们,意味着你获取重复的值,将它们保存为子节点,0、1和2是这里每个游程的值,然后你还编码这些游程在原始数组中结束的偏移,所以是实际结束的索引。 这样,我们有字符串值作为子节点,但我们也有多个整数值,我们可以看到所有这些值都非常小。游程结束的值子节点,它们都小于4。4是2的二次幂,所以你可以用两位编码每个。结束和偏移都小于8,所以你可以用三位编码它们。你将所有这些整数数据位打包,最终得到磁盘上的实际位。这意味着总共约30位,所以这少于一个整数。它少于4字节。然后使用这些信息,你存储输入数组的所有重复和嵌套结构,并且你只存储唯一元素一次。我们可以进一步压缩。例如,我们有FSST,是一种提取公共子串然后进一步划分为数组的算法。 对于这个例子,我认为这说明了要点。我想将这两件事分成两种方式。你有数组,这是一种结构,是你如何编码这个段。然后,这很重要,因为这是你用来分派计算的东西。在右侧,你有数据。我想将这两者视为控制平面和数据平面,这将在扫描中很重要,我们稍后会讨论,但在此之前,我想提一件事。手头有这棵数组树,并且有这些数据作为指针在某个位置,你可以在不解压的情况下分派计算。例如,我想进行随机读取。我想获取第一个列表的第二个元素。如果你将该查询传入ListArray,ListArray知道它可以二分搜索偏移子节点以找到实际位置,然后将其传播到展平的值子节点,因为它现在知道实际位置。 DictArray可以对代码做同样的事情,然后在返回之前替换为正确的值。另外,我提到一些在压缩数据上运行的计算可能比在解压数组上运行更快,比如求和。求和需要在解压数组上迭代每个元素并累加值。如果你有一个游程结束数组,那只需将每个游程的长度相乘,然后每个游程,你累加乘法的总结果。在压缩数据上运行计算要快得多。 Vortex扫描/字节复制 回到分离。控制平面显示你在计算引擎中需要的信息量,以便你可以在数据本身上分派工作。数据平面是你必须传输到计算引擎的信息。它们服务于不同的目的,我们不希望它们互相干扰。我们如何做到这一点?Vortex有一个扫描,试图 最大化整个吞吐量。它的工作方式是尝试最大化I/O引擎和计算引擎的工作,使它们一直忙碌。然后,扫描是编排层。编排本身也浪费计算,必须分派工作,重建数组树,然后一直遵循数组树逻辑到最后。I/O层然后向计算源获取一些字节,返回指针,扫描然后在这些指针上分派工作。 一个小细节,但计算也可以请求更多段,因为例如,在我们最初做的查询中,选择年龄大于30的名字,你在执行年龄过滤器之前不知道是否需要名字列。它可能被完全修剪掉,所以你最终不会获取那些段。假设我们在扫描中有一个查询,然后我们在扫描中有布局,所以我们知道需要获取哪些段。这些被转换为字节范围,这些是你想从源获取的字节范围。每个单独的字节范围可能有延迟,并且取决于你读取的源,你不想单独分派每个字节范围。我们在上面有一个合并层,合并意味着如果两个字节范围足够接近,你合并它们,然后有一个单一的字节范围。 你得到中间额外的字节,但因为你支付一次延迟并获得整个吞吐量,这对你有利。这取决于源。NVMe对某些合并配置满意,但例如S3可能需要更多。S3,你可以做16兆字节的段,因为它有巨大的吞吐量和巨大的延迟。一旦你得到段请求,然后有字节范围,你就在它们上分派工作。这是一个CPU扫描。当你有RAM中的字节时,这项工作就完成了。然后你分派的工作是常规的Rust函数,如切片、投影、过滤,或输入查询希望你做的任何计算。那是CPU。当你扫描到GPU时,大多数事情保持不变。扫描编排保持不变,合并层保持不变,因为我们仍然需要那些字节范围,但现在I/O平面必须将这些缓冲区一路运送到GPU,并返回指向GPU的指针。那时它的工作就完成了。计算,现在不是分派Rust函数,而是分派CUDA内核,但其他一切都相同。 我们在中间有一个缓冲池,我想解释为什么我们在中间有一个缓冲池。这是字节范围从S3到GPU的扫描生命周期。最初,当你从S3请求字节时,它落在内核接收缓冲区上。这是一个直接内存复制,所以它不是实际的复制,它是这些字节从网卡到达的第一个地方,是这个内核接收缓冲区。它立即将密文(仍然是TLS加密形式)复制到用户空间,一对一。然后我们在HTTP库领域,使用Rust TLS和请求。Rust TLS将其解密为明文副本,这是一次复制,然后它仍然是TCP,我们想从中获取HTTP主体,所以请求层解帧,将主体放入自己的缓冲区,最后它到达我们自己的Vortex领域。 因为这些黄色缓冲区都是流式的,因为它们是分段GET请求,我们必须聚合它们,因为我们想要段,我们想要连续的字节范围。我们聚合它们,所以我们必须再复制一次,但这是我们做的唯一复制。我们以流式方式接收这些块,然后我们累积它们直到请求完成。在此之后立即,我们分派CUDA。我们进行CUDA异步复制,并期望它落在GPU上,但我们不知道,在内部,CUDA做了另一次复制。它做另一次复制,因为它需要我们的一些对齐规则。基本上,你可以从主机复制到GPU的缓冲区必须是固定的和页面锁定的,意味着它应该在操作系统的虚拟页面系统之外,所以如果缓冲区在复制期间被换出,GPU不会段错误并保持快乐。 在深入探讨如何减少复制之前,我想提一件事。memcpy成本对我们很重要。单位是每吉比特带宽你能维持的核心数。这很重要,因为这个核心预算也被扫描中的编排层使用,如果你在CPU上做计算,也被计算使用。它有机会成本,因为你有有限的CPU,如果你只是浪费在memcpy上,那么你减慢自己的带宽。这就像双重伤害,所以你想尽可能减少这个。 我们如何减少这个?最初,我们从CUDA反弹缓冲区开始,因为我们知道要求。我们可以满足这些要求在我们自己的缓冲区上,我们累积HTTP缓冲区到其中。我们有一个固定的缓冲区,然后我们在其上调用CUDA异步复制。它不使用反弹缓冲区,这直接进入GPU。进一步,因为分配这些很昂贵,我们保留一个缓冲池来分摊分配成本。我们重用这些缓冲区的方式是当你分派CUDA复制时,你在该CUDA流之后附加CUDA事件。你附加一个事件。一旦该事件触发,你知道它之前的所有内容都完成了,所以你可以安全地回收缓冲区,因为这意味着传输完成,你可以为管道中的下一次复制重用同一个缓冲区。这减少了一些成本,但我们需要然后看左边,HTTP领域,以摆脱一些复制。 你可以在这里做的一件事是,如果你有足够高的Linux版本,你可以使用kTLS,内核端TLS解密。这将解密推入内核,并且因为内核立即复制到用户空间,那么它不再需要这样做,因为现在它已解密,然后解密可以在原地进行。解密可以在接收缓冲区本身中原地进行,所以它节省了一次复制。有一个警告,Linux密码在kTLS中大多不是最新的,所以它们可能不会使用你的机器拥有的最宽SIMD寄存器,但尽管如此,这里的memcpy节省了,因为我们减少了一次复制,在某种程度上抵消了那个缺陷,所以我们仍然在这个领域保留kTLS。 接收缓冲区本身,所以它节省了一次复制。有一个警告,Linux密码在kTLS中大多不是最新的,所以它们可能不会使用你的机器拥有的最宽SIMD寄存器,但尽管如此,这里的memcpy节省了,因为我们减少了一次复制,在某种程度上抵消了那个缺陷,所以我们仍然在这个领域保留kTLS。 还有什么可以改进?我们可以在HTTP方面改进更多,但为此,我们需要做更大的改变,因为这里没有容易实现的事情。如果你做更大的改变,即编写自己的HTTP客户端,那么它回报很好,因为如果你编写HTTP客户端,你控制你给内核的缓冲区。如果你这样做,你可以使用已经固定和页面锁定的Vortex缓冲区来累积和解帧,在同一个缓冲区中即时进行。一旦你获得解密字节作为分段GET请求中的流,你不断将它们分配到同一个缓冲区,你已经准备好发送到CUDA。然后当分段GET请求完成时,你分派工作,你循环这些缓冲区,所以通过一次复制,你从S3到GPU,并且在整个扫描期间CPU开销非常小。 还有这个的一次迭代,但你必须摆脱TCP。如果你有一个非常昂贵和繁琐的设置,但真正高性能和酷的RDMA对象存储在你手中,那么你可以使用RDMA协议,它与TCP不同,它不必经过内核套接字层。它可以完全绕过CPU,因为它作为两个对等方通过某种光纤连接,可以是PCIe,可以有某种网络连接,可以在中间,但它们可以读写彼此的内存区域。如果你有这种能力,那么GPU本身可以代表你分派I/O请求。如果它这样做,它不仅绕过所有CPU,甚至绕过你的CPU的PCIe集线器,因为这些盒子带有多个GPU和许多网卡。大多数情况下,每个GPU都有一个关联的网卡,所以RDMA将从GPU流经最近的网卡,完全绕过你的CPU PCIe集线器,所以它基本上是免费的。CPU甚至不知道发生了什么。为此,网卡也需要足够智能,但大多数带有高端GPU的盒子已经配备了该网卡。 有了所有这些,让我们回到视频。现在,你可以看到有一些字节范围合并,所以我们有过滤器或投影。在这种情况下,我们没有过滤器,我们选择*,本质上,所有列,红、绿和蓝。它们都经过这个合并层。然后我们从S3获取这些字节,然后我们循环我们读取它们的字节缓冲区,它们只进行一次复制进入GPU,然后GPU以每秒15吉比特、4K、60赫兹处理它们,然后我们流式传输回来看看发生了什么。关于这个也很酷的是,因为当我们拥有它时,我们可以更改过滤器和投影。我们可以做,例如,这个。这不是后投影。这实际上是投影和自定义Vortex表达式的组合。我写了这个自定义表达式称为量化,我为它写了一个GPU内核,这是非常基础的。 量化只是获取颜色,每个像素值是0到55,然后它将其捕捉到三个级别,基本上,取决于它最接近哪个级别。你得到只有三列的效果。我们还修剪了蓝色列,只是为了好玩,所以它更黄,但这也是我们拥有的离散化镜头。这很重要,主要由Vortex编排处理。你不会自己将内核绑定在最后。你只是说我想这个查询,扫描编排负责附加你的内核,然后分派工作到你的内核,然后你可以保持相同的吞吐量。或者你可以做类似的事情。这看起来有点刺眼,因为视频到处跳。这样工作的方式,记住我告诉过你当我们有这个文件有三列时?这个文件有四列,第四列是帧是否有骑自行车的人。 我已经对每帧运行了这个对象识别算法,然后我们现在有这个has_bike布尔列,作为第四列,我正在过滤到只有有自行车的帧。这样工作的方式是因为每帧都很大。我们有的每帧是8兆字节。我们可以负担得起每帧有一个区域。在布局级别,我们有一个区域映射,它尽可能细粒度。我们每帧有一行,所以我们可以修剪掉没有骑自行车者的每个单独帧。 也许这里更重要的是所有这些更改不影响吞吐量。你可以提出任意过滤器。你可以提出自己的投影。你可以组合它们,但你可以保持相同的吞吐量,并且你只读取你需要的数据。记住我告诉过你视频是每秒60帧,因为我故意放慢它们。通常,当你让它们自由然后写入文件时,那么我们可以流式传输每秒240帧,因为我调整视频速度以便我们可以每秒60帧看到它,但我们可以用Vortex端到端维持每秒60吉比特。 什么是可能的? 我想非常简短地谈谈如果你想自定义Vortex,你可以做什么。记住我告诉过你,你可以有自己的数组和自己的布局。布局负责I/O和逻辑计划以及修剪和所有那些东西,但它也负责放置。Vortex文件规范中没有任何东西强制你在同一个物理文件中。你可以将你的段分成两个。你可以获取修剪段并将它们存储在Redis中,你可以将所有数组段存储在S3中。Redis的延迟是S3的百分之一,所以你几乎立即得到修剪,与你会从S3得到的数字相比,然后你得到S3的吞吐 量在将数据流式传输到端。或者,假设你有这个巨大的实例,带有多个高端GPU但单个CPU,然后你有这个GPU分析引擎,你将数据分片到各个GPU,然后你运行扫描,然后将扫描分派到各个分片。 如果查询有聚合呢?然后你需要将所有结果聚合到单个GPU。你可以将该聚合建模为RDMA扫描,因为GPU通过称为NVLink的极快光纤相互连接。它可以维持惊人的速度。你可以从一个GPU扫描到你的中央GPU进行聚合,并且你可以以每秒太比特的速度进行扫描以进行聚合。 要点 那就是Vortex。我想谈谈它如何允许你更改过滤器,更改数据混合,而不必一遍又一遍地重新处理所有内容。我还想提到你可以从S3读取到GPU,速度接近网卡速度。可以是每秒60吉比特,如果你有更强的网卡,可以更多。你可以从RDMA源读取到你的GPU,以你拥有的任何PCIe连接,那可以是每秒500吉比特或使用最新的PCIe达到太比特。 问答 参与者1:关于视频示例,我试图理解的是,你似乎不将视频保存在S3中,而是完全分离的RGB帧,并且你不应用任何视频编解码器来压缩数据,然后会有I帧、P帧。你明确提到每帧8兆字节? Onur Satici:是的。 参与者1:这是Vortex有多好的合成示例,还是真的有从S3获取属性的用例? Onur Satici:那是一个假设的例子。Vortex不是视频编解码器,它只是为了演示我们可以端到端维持的吞吐量,并且我们可以将扫描可视化为视频格式,碰巧是视频,但Vortex可以这样使用。它主要面向你通常使用的分析或训练输入数据。 参与者1:那么Vortex对压缩编解码器有用吗? Onur Satici:你是指块压缩,如zlib? 参与者1:如果你碰巧有视频流,甚至JPEG,分离但压缩的数据,将它传入Vortex列以获得其他好处有价值吗? Onur Satici:是的,你可以。对于压缩的JPEG,你将无法对它们运行任意计算,但你可以将相机的传感器流建模为Vortex文件。我们支持许多宽列,所以你可以有一个负责这些JPEG图像的列。更酷的是有某种透明压缩,所以你可以,例如,裁剪,然后只获取该裁剪中的字节范围。布局允许你这样做,但你需要使用正确的图像压缩。 参与者2:数据库引擎采用这个吗? Onur Satici:我们支持DataFusion、DuckDB和Polars。我们也在努力为Vortex获得Iceberg支持。正在开发中,是的。DuckDB和DataFusion是稳定的。你今天就可以使用它。 参与者3:你刚刚评论了Iceberg。你有更多细节,我们什么时候可以期待? Onur Satici:是的。我们正在与Iceberg合作,以拥有兼容的官方Vortex支持。Iceberg正在抽象文件,将Iceberg从自身中抽象出来,所以我们正在与他们迭代以尽快发布,但它仍在开发中。 参与者3:一年前我在你的博客文章中读到了它。 Onur Satici:正在进行中,是的。 参与者3:正在进行中。 Onur Satici:是的。 参与者2:我有一个稍微不同的问题。我碰巧在与LinkedIn的一些人交谈时提到了这一点,然后他们的回应是,你的公司让你将GPU用于纯ML工作负载以外的任何东西吗?这似乎是分析数据加载的巨大加速,但似乎公司倾向于将预算专门分配给GPU上的ML工作负载。 Onur Satici:我认为它可能对其他分析用例更好,因为ML工作负载,你从重新处理数据中节省,但Vortex可以维持的速度,消费模型,如果是预训练模型,真的很慢,所以它不需要那么多带宽。如果你有一些在GPU上发生的分析工作,那将从更高的吞吐量中受益更多,我认为。 参与者4:首先,臭名昭著的互操作性,我想,我选择列式格式,如Parquet,例如,它给我相对广泛的互操作性。在某种程度上,如果我必须提出一个有说服力的叙述,为什么我应该选择可能没有相同互操作级别的替代列式格式,你的首要论点是什么?另外,我的第二个问题是,你谈到了移动数据时的网络惩罚和CPU惩罚。网络惩罚几乎不是S3和S3 API工作方式的函数吗?如果你有更好的源,你不会在某种程度上避免一些网络惩罚吗?CPU惩罚,我理解,你几乎总是会得到它。 Onur Satici:对于互操作性,是的,我们有对DuckDB、DataFusion和Polars的官方支持。我们正在努力集成更多。有了Iceberg,我认为你会解锁过渡会更容易,因为Iceberg将文件格式从你那里抽象出来。这是主要思想。如果你想尝试Vortex,从Parquet转换真的很容易,只需自己测试看看。 另一个问题是关于网络限制。S3,令人惊讶的是也需要很多调整。你可以更改字节请求,甚至你发送到S3的字节范围请求的顺序,会改变你能从S3获得的吞吐量很多。甚至你必须调整网卡,你必须将正确数量的ENI绑定到实例中的网卡,等等,以最大化吞吐量。是的,大多数时候,当调整正确时,S3本身不会是你的瓶颈。 这个流程中的瓶颈。 参与者5:你展示的大部分剪枝操作似乎都依赖于数据首先被排序。我们最终遇到的很多问题中最大的一个是,很多不同的用户需要相同的数据以10种不同的方式排序。你能使用类似布局的东西来基本上保留你需要的所有元数据,这样你就可以有效地让相同的数据以10种不同的方式排序供人们阅读吗?我不确定这样是否高效。你仍然需要选择一个最高效的选项。 Onur Satici:是的,那会是一个选项。我们主要将排序本身委托给我们正在使用的计算引擎,比如DataFusion或DuckDB,它们会处理排序。是的,我认为这是一个选项。 参与者6:我有一个关于我们在布局中存储的元数据的问题。我们讨论了特定字段的最小值、最大值。我们如何决定暴露哪些元数据?在年龄的情况下,查询是设计好的,最小值、最大值就在那里。一般来说,你如何编写Vortex文件,知道什么可能会让它更好? Onur Satici:这实际上对你来说是透明的,所以你不需要选择哪些区域映射会最终出现在文件中。我们支持的统计信息,我只展示了一部分,比如任何你可以在写入时计算并作为统计信息,之后可以用来剪枝的东西,我们都会排序。比如空值计数,例如,最小值、最大值,例如。你甚至可以在文件中进行基数估计,这样可以帮助聚合等操作。任何有助于剪枝的东西,我们都尝试集成。在Vortex中添加一个新的非常容易。 参与者2:在你的一张幻灯片中,很早的时候,你有字典编码,三种颜色。我想你说数据有4个字节,但你没有计算字典本身的字节数? Onur Satici:没错。只是开销大约是30位左右。是的,所以这大约是30位,再加上这个。如果你有正确的编码,你可以进一步压缩。 也许为了结束关于S3的问题,实际上,也许与直觉相反,但如果你调整得当,S3的带宽可以超过NVMe的带宽。例如,在我展示的那些扫描中,你可以从S3扫描比从本地NVMe扫描更快。初始延迟更高,但从S3的吞吐量也更高。 参与者2:我记得当Parquet刚出来的时候。那时大数据世界正试图与成熟的MPP数据库竞争。他们做的第一件事之一是说,行主序不太好,让我们转向这种列式格式。他们说的下一件事是,如果我们使用JDBC,我们正在把列式的东西转回行主序。然后我们就没有通过内存、通过所有层到寄存器进行流水线处理的任何好处。这就是Arrow出现的地方。Arrow在这里扮演什么角色? Onur Satici:所有压缩的Vortex,我们称之为规范编码,对于每种逻辑类型,我们都有一个规范编码。它们与Arrow一一对应,并且与Arrow之间是零拷贝的。这也是我们如何接入DataFusion的方式。DataFusion需要Arrow批次。我们只需解压Vortex数组,获取Arrow批次,然后继续处理。如果你有,例如,DuckDB,DuckDB有多种类型的向量。有一个常量向量,我们也有一个常量数组。它已经是一个常量数组了。不需要解压然后由DuckDB重新编码为常量数组。我们直接传递过去。 参与者2:假设我拿一个Parquet文件并转换为Vortex。我想它有关于快多少的统计数据。 Onur Satici:是的,我有数字。 参与者2:我想之前,你有大约30倍快或类似的。 Onur Satici:是的,我有GPU扫描的数字,但我也有CPU扫描和写入、读取以及随机访问的数字。这表明GPU扫描快30倍,随机访问快100倍。CPU扫描大约快20倍。CPU写入,写入Parquet文件大约比Parquet快5倍,在那个范围内。 参与者2:我想,对于另一个问题,Avro已经存在很长时间了。我在LinkedIn工作了很多年,Avro是标准。然后有大量数据,但他们将其转换为Parquet,因为那是有意义的。最终,将所有Parquet转换为Vortex也会有意义,如果它在CPU和GPU上更快的话。实际文件大小有差异吗? Onur Satici:大小差异?压缩比与Parquet非常相似。大约多10%或少10%,在同一个范围内。 参与者2:有Parquet的压缩,然后还有压缩,比如Parquet ZSTD,它更进一步。你是将Parquet转换为Vortex吗? Onur Satici:ZSTD Parquet到Vortex。 参与者2:Parquet ZSTD到Vortex? Onur Satici:是的。 参与者2:你不需要在Vortex上应用ZSTD吗? Onur Satici:不需要。 参与者2:这样做会有什么好处吗,还是基本上你不会得到太多压缩? Onur Satici:布局也有写入器和读取器。我们有一个自定义的布局写入器,你可以使用,称为紧凑写入器,它对某些数据使用zstandard,但例如,对浮点数使用Pcodec。如果你想要更小的文件,你可以调整它,但那样你会失去你得到的计算优势。这取决于你。Vortex是完全可配置的。你可以有任何写入器或读取器,并按你的意愿组合它们。 参与者2:这是V1还是V子1? Onur Satici:是V1。我认为六个月前就稳定了。 参与者2:你是这个项目的提交者、维护者。 Onur Satici:是的。 参与者2:如果我们看看Protobuf,Protobuf 3超过20多年。每7年有些东西发生了变化,变得与之前不兼容。 你对Vortex的看法是什么?一件事是如果我们看GPU,它们有虚拟化层。你针对那个虚拟化层编码。GPU变化非常频繁,但CUDA编码根本不变。你如何看待你的格式?你需要跟上什么?因为如果你针对那个层编码,而不是针对GPU硬件本身,那么你是标准的。你必须改变这个以充分利用硬件本身。硬件变化非常快。你如何看待Vortex随时间推移的向后兼容性? Onur Satici:文件规范是标准的,并且故意保持最小化。我们不需要更改文件规范来改变大多数其他格式需要文件更改的事情,例如。比如Parquet花了大约7年时间整合f16,因为委员会,背后有很多大公司,必须同意一种或另一种形式。或者ALP,例如,是一种很酷的浮点压缩。它已经开放了几年了,而且远未合并。对于Vortex,它只是一个crate。你只需编写一个新的Rust crate,那就是你的新布局。文件的核心部分不会破坏,因为它非常简单。你可以有一些其他人编写的新编码,而你没有,所以你不能读取他们的文件,但你只需添加库然后读取它们。 参与者2:你支持非Rust的Serde吗? Onur Satici:是的,我们有FFI绑定。我们甚至有Java绑定。是的,任何语言,你都可以与Vortex连接。 参与者7:你举了一个数组和字典的例子。最终,你仍然在单个列内保留嵌套数据结构。你在列内优化它们,但你仍然不尝试在嵌套结构的碎片化上扩展它们,构建列,或在那里实现更好的压缩。Vortex与Parquet相比,对于映射或JSON这类数据结构,是否有改进步骤? Onur Satici:我们目前核心中没有这个,但可以很容易地作为自定义布局添加,因为碎片化就像你自己编写的一个新布局。它会将JSON blob视为二进制,然后提取它,然后将你想要的字段存储为单独的列。这是一个很好的扩展点。只是还没有在核心中。 参与者7:是还没有在核心中,还是设计如此,不是你想要解决的问题? Onur Satici:我们认为压缩比以及查询模式,例如在TPC-H、TPC-DS和ClickBench中,这种压缩到目前为止效果很好。如果我们遇到一个会从碎片化中大大受益的用例,那么通过一种新类型的布局来集成并不难。 参与者8:我有一个关于Vortex文件的一致性和错误纠正的问题。你使用哪种机制,以及当你读取文件时,如何识别文件已损坏,因此需要重新下载或重新创建? Onur Satici:目前,默认情况下,我认为我们依赖网络栈。你可以在页脚上添加Merkle树和奇偶校验作为元数据,如果你愿意,你可以让你的布局在反序列化文件之前检查它们。 查看更多带文字记录的演示文稿 录制于: 2026年9月4日 由 Onur Satici 相关赞助商 此内容属于AI、机器学习与数据工程主题 相关主题: 开发 架构与设计 AI、机器学习与数据工程 S3 列式数据库 QCon London 2026 Rust AWS 系统编程 Amazon 数据库 QCon软件开发大会 文字记录 NoSQL GPU 流处理 数据湖 数据管道 性能 CUDA 机器学习 架构 InfoQ 云计算 相关编辑内容 InfoQ上热门内容 Kubernetes推广KYAML作为更安全、更一致的处理清单的方式 Airbnb通过服务器驱动架构削减60%的认证代码 OpenClaw 2.0发布,简化设置并支持协作代理 Google Mantis:一个用于减少误报的代理漏洞扫描工具 Java新闻综述:TornadoVM 6、JReleaser、LangChain4j、Java Operator SDK、JHipster、Yupiik Fusion 个性胜过技能:Adam Wachtel如何构建工程团队
查看原文(中文正文为 AI 翻译)
InfoQ Homepage Presentations From S3 to GPU in One Copy: Rethinking Data Loading for ML Training AI, ML & Data Engineering From S3 to GPU in One Copy: Rethinking Data Loading for ML Training View Presentation Vertical Horizontal Full Speed: 1x 1.25x 1.5x 2x Download Slides 49:37 Summary Onur Satici explains how Vortex, an open-source columnar file format under the Linux Foundation, revolutionizes high-throughput data loading. He details how cascading lightweight encodings, layout-based segment pruning, and zero-copy memory pipelines eliminate CPU/NVMe bottlenecks to stream S3 data straight to GPUs at speeds up to 60 Gbps without requiring upfront data reprocessing. Bio Onur Satici is a Staff Engineer at SpiralDB and a core maintainer of Vortex, an open source columnar file format now part of the Linux Foundation (LF AI & Data). He focuses on high-performance data systems, GPU acceleration, and making analytical workloads faster at every layer of the stack. About the conference Software is changing the world. QCon London empowers software development by facilitating the spread of knowledge and innovation in the developer community. A practitioner-driven conference, QCon is designed for technical team leads, architects, engineering directors, and project managers who influence innovation in their teams. Transcript Onur Satici: Before I get into anything, I want to show you this video. Not specifically like what's playing in the video, but how we produce this video. Because this is not backed by a regular MP4 file in some main disk, but this is actually a visualization of a file scan. What I mean by this is we have a Vortex file, Vortex being a columnar file format. It has three columns, one per color. Each column would then have a row per frame of this video. Each frame would include around 8 million pixels, because it's a 4K video. Then we then process this data, which is three columns, around 8 million pixels per frame, 60 frames per second, so around 13 gigabits per second, from S3, through the network card, over the CPU, and to the GPU. The GPU is seeing this data this fast, but we want to see what's happening in the GPU, we want to visualize that. We can't downlink that much data to our laptops, so because it's already in the GPU, I'm using the GPU itself to re-encode it into H.264, wrap it into TCP buffers, send it over my SSH tunnel, and we can display and see what's happening. Now have a look at this. This is the same video, but this doesn't have any post-processing on it. This is actually a visualization of a column pruning, or projection pruning, where we only select, in our projection expression, one column, and only the bytes that are associated with that column are flowing through the same pipeline, keeping the same bandwidth, and then ending up with the video that you were seeing before. Today I want to talk about how this is possible, and the technologies that enable us to do this. I'm Onur. I work at SpiralDB. I help maintain Vortex, which is an open-source file format that is under the Linux Foundation. I want to talk about what makes Vortex special. I'll start with why we need this speed in the first place. I'll then move on to Vortex itself, explain some of the main topics around Vortex, layouts and arrays and the scan itself, and then end with what is possible. Problems in Data Loading to GPUs To start with the problem, why we need the speed. Because you have some GPUs that you hired and you want to use them. GPUs are expensive, and then each time they're idling, it's expensive, and it's an opportunity cost of not doing valuable work. I want to tell this like we have two types of taxes that we pay when utilizing GPUs. The first one is a movement tax. Getting the data from some source that you have into the GPU is not trivial, and it's not really cheap. If you use web dataset for training, or for example, MosaicML's streaming dataset, then they go more or less these steps. For example, let's say your data is in S3. You fetch from S3 into the disk, into NVMe. From NVMe, you read compressed bytes into your RAM. You decompress on the CPU, and then send those decompressed bytes over the host of device link, PCIe, to the GPU. This not only has many steps that you have to follow through. If you zoom in into the supported bandwidth per connection, you can see that we are bottlenecked mainly by CPU decompression, and the NVMe throughput. The reason why they all persist into NVMe is the main consumer of this pipeline is PyTorch. PyTorch is Python. Python doesn't like multi-threading, so therefore we use multi-processing. Each worker doesn't want to step into each other's toes, so they want to deduplicate the downloads, so they persist the shards on the disk. Ideally, we want this pipeline to look more like that, where you fetch from S3, eliminating the bandwidth bottlenecks through the network card speeds and the PCIe speeds to the GPU. Then, further, you want to use the right compression that enables you to decompress in the GPU, the parallel compressions, and GPU can decompress much faster than CPUs. You're not only eliminating the bottlenecks you had in NVMe, and you're streaming much faster now, you're streaming maybe 50 gigabits per second, but those bytes are compressed. If you pick the right compression, effectively, this pipeline runs through 10 times faster than the actual compressed speed. That was movement. There's another tax I want to talk about, which is decision. It is when you want to iterate on your training workload. You've loaded from your source into the GPU, and maybe you did some preprocessing to your training, then you want to evaluate the results. What you want to then try, for example, is a new data mix, or you want to change the curriculum. You want some new filter on this data. With the current tools we have, you have to reprocess the entire data, probably re-tokenize, reshuffle to get the right order, and then load again to your source, which might be S3, might be somewhere else, but this is slow. If you look at the hypothetical pie chart of your percentage of time spent, you want to maximize the gray, where you're thinking, and the green, where your GPU's working. Everything else is overhead. You want to turn this into that, where you load from your source into the GPU without wasting much time. Then once you evaluate and decide you want to change something in your input source, then you just change your scan. You just give a different query, a different filter. You don't need to reprocess. Then you immediately can start training for your next iteration. Vortex - Overview How does Vortex, using a different file format, enable all this? Vortex, to give a quick overview of what it is, it's a columnar file format. It is similar to Parquet, but it's also much different than Parquet. It has an extensible spec, a minimal spec that you can plug in into various points. It has a separation of logical types versus physical types. That's one of the main differences from Parquet, is that Vortex doesn't have a notion of tying the types themselves, like integers, into how they're encoded on disk. It's up to you, it's decoupled. It does as little as possible, as late as possible, saving a lot of memcopies for materializing intermediate results, and leveraging from query optimizations. Then it can push through most of the projections, filters, and aggregation from your compute engine into the file format itself. Also, the way it does compression is different. It doesn't use block compression that loses the meaning of the underlying data. It does use lightweight and cascading encodings that can also give a similar compression ratio, but more importantly, they allow you to do compute on the compressed data. You can do random access, you can do aggregations. Some of these computes can be even faster than doing it on the decompressed data itself. To give some concrete numbers versus Parquet, this S3 to GPU scans that we are showing is around 30 times faster. Thanks to the encodings and the compression Vortex has, it's around 100 times or more faster than Parquet in random access. What does the file look like? We will approach this from a file reader's perspective. There's a Postscript that's fixed size, you read it, it has a lot of pointers into other sections of the file. There's a footer. The footer itself is a legend, it's a bill of materials of what you expect to see in this file. You have a list of arrays and you have a list of layouts. Arrays are how you encode the data itself in Vortex, and layouts are like the logical plan. They are the I/O layer of pruning. We will dive into that later. These are showing you the main plug-in points. If you have a new array of a new layout, you just add it here, and you have the logic in your reader, then Vortex will delegate to your reader to do what you want to do. Thirdly, and maybe most importantly, it's the list of segments that you expect to see in this file, and the segments are the majority of the file. They have the array data themselves. They come with triplets, so offsets, length, and alignment. They're pointers, but they also have alignment. Alignment is important to bake in the file, we think, because if you have the alignment, you know that if you want to use SIMD operations or GPU kernels when you load this, if you know the alignment beforehand, in your receiving buffer, you allocate that buffer with the right alignment, so you don't need to copy, just to dispatch the compute on top. I've talked about arrays and layouts, but what are layouts? Layouts enable you to prune those segments as quickly as possible, given a filter and projection. They are like the logical plan. They are also very minimal. This is the entire flat buffer definition. The only two maybe important points I want to touch is the last two fields. It has the children, which is an array of itself, so it's a tree, and each node in this tree has segments. Segments is a list of integers, and those integers point to the offsets of segments. We know from the footer where they are in terms of pointer, like exact byte ranges. Each node in the layout tells, I am responsible for reading these segments, and give me a filter, and I'll tell you which of these segments I'm responsible for, you have to read. Vortex Layouts To give an example on how layouts work, let's start with a schema, for example. We have the root layout. It has two columns, a name, string, an age, integer. The StructLayout only tells it, I have a child per column, so you expect it to have two different children, one per column. Each of these is a ZonedLayout itself. ZonedLayout is interesting because it not only contains the data as segments, it also contains another segment that is a summary statistics of the data that you expect to read. That's also a segment itself. When you read that segment, it's a zone-map. A zone-map is a Vortex array by itself, but it does have a per row range in the actual data, and has columns of statistics, like min, max. The data is then just a concatenation of multiple segments. It's a way of breaking it down into smaller arrays when you want to read them. How does this work, then? Say you have this query. You want to get the name column, but you only want to get the rows where the age column is greater than 30. You start from the root. Root knows it has a projection and a filter in each column, so it delegates into each ZonedLayout through this. The age will receive a filter, and it will then initially load the zone-map, check the zone-map, check the filter, find which segments in this data passes the filter. Let's say the last segment passes the filter, so then it returns the last segment. We also get the name column to return the last segment, because that's the actual data we need. Do the actual filter on the actual rows, and return the rows that we need. By reading the zone-map and only 30% of the segments, you can satisfy this query. Maybe this is a more detailed view. The center is the zone-map. That was a segment. We read it. We deserialized it. It's an array. It has one row per row range, and we have the filter. By just looking at the max value, we can prove that only the last row here can have any rows that match this filter. All the others you don't need. You read the entire chunk. That's the Vortex atomic unit of reading. It's a segment inside. Then, what's neat about Vortex is the compression we have is sliceable. You don't need to decompress everything. You slice it first, and then you only decompress the rows that match the row range we have it. The last 8k rows is decompressed. That is like doing as little as possible and as late as possible. The difference between chunk granularity and row granularity is also, I think, important to mention, because these serve different purposes. Chunk granularity is an I/O concern. It deals with what type of I/O source you have, what is bandwidth and latency characteristics, so you can tune them in Vortex. The zone granularity itself is a compute concern because, depending on your architecture, you might have different wider SIMD registers that you want to use, and those play well with different lengths of data that you have. You can tune them as to your liking with Vortex. Vortex Arrays We talked about reading the segments and layout is helping us prune to the segments that we want, but what do each segment contain? They contain a serialized, most likely compressed Vortex array. A Vortex array, I want to also explain with an example. Let's say we have this array. It's an array of arrays, so the element is a list itself. The elements inside the inner lists are strings. We want to encode them and compress them as much as possible while keeping the meaning. We can start with a ListArray that's similar to Arrow. It basically flattens the values and saves the flattened values as a values child, and then it keeps the inner brace locations because it removes them while flattening, it loses the information, so it keeps that information into the offsets child. Any two consecutive offsets would point to the inner braces of an inner list location. You can see this is also a tree. Now we have the ListArray, but two children are also arrays themselves. We can go on further, we can compress further. For example, the values child has only three unique elements so we dictionary encode them. Dictionary encoding gets the unique values and stores them as children, so like in a values child has one city appearing once only, but you then encode the repetition of the original array as the indices that point to the values child. Then you decoupled the values themselves and the repetition into separate children. Then you can continue further because the codes now have contiguous repeated values, which are called runs. You can run length encode them, or in Vortex, more specifically, run end encode them, meaning you get the values that repeat, save them as a child, 0, 1, and 2 are the values of each run here, and then you also encode which offsets in the original array these runs end, so the actual indices that end. With this, we have the string values as a child, but we also have multiple integer values that we have, and we can see that all of these values are really small. The values child of run end, they're all smaller than four. Four is second power of two, so you can encode each of them in two bits. The ends and the offsets are all less than eight, so you can use three bits to encode them. You bitpack all of this integer data you have and end up with the actual bits on disk like that. That means this is total 30 bits or so, so this is less than one integer. It's less than 4 bytes. Then using this information, you are storing all the repetition and nested structure of your input array, and you only store the unique elements once. We can further compress this. We have FSST, for example, is an algorithm that extracts the common substrings and then further divides into arrays. For this example, I think this gets the point. I want to separate these two things into two ways. You have the arrays, which is a structure, which is how you encode this segment. Then, it is important because this is what you would use to dispatch compute. On the right-hand side, you have the data. I want to think of these two as the control plane and the data plane, and that will be important in the scan, which we'll go into later, but before, I want to mention one more thing. Having this array tree on hand and having this data as pointers somewhere in some location, you can dispatch compute without decompressing. For example, I want to get a random read. I want to get the second element of the first list. If you get that query into the ListArray, ListArray knows that it can binary search the offset child to find the actual location, then does propagate that to the flattened values child because it now knows the actual location. DictArray can do the same for code, and then substitute with the right value before returning. Also, I mentioned some compute you can run on the compressed data might end up being faster than running the compute on decompressed arrays, like summing, for example. Summing requires, on a decompressed array, iterating through each element and summing the values. If you have a run end array, that's just multiplying with the lengths of each run, and then per run, you are summing the total result of the multiplication. It's a lot faster to run the compute on compressed data. Vortex Scan/Byte Copies Back to the separation. The control plane shows you the amount of information you need in a compute engine so you can dispatch work on the data itself. The data plane is the information you have to transfer to your compute engine. These serve different purposes, and we don't want them to step in each other's toes. How do we do this? Vortex has a scan that tries to maximize this throughout. The way it works is it tries to maximize the I/O engine's work and the compute engine's work to be busy all the time. Then, scan is the orchestrating layer. The orchestrating itself also wastes compute and has to dispatch work, reconstruct the array trees, and then follow the array tree logic all the way to the end. The I/O layer then gets some bytes to the compute source, returns the pointers, the scan then dispatch work on those pointers. A small detail, but compute also can request more segments because, for example, in the query that we initially did, select name where age is greater than 30, you don't know if you need name column before executing the age filter. It could just be pruned all together so you don't end up getting those segments. Say we have a query in the scan, and then we have the layout in the scan so we know which segments we need to fetch. Those are transferred into byte ranges, and these are byte ranges that you want to get from your source. Each individual byte range might have a latency, and depending on what source you're reading, you don't want to dispatch each individual byte ranges by itself. We have a coalescing layer on top, and coalescing means if two byte ranges are close enough sufficiently, you merge them and then have a single byte range. You get extra bytes in between, but because you pay the latency once and get the entire throughput, it works on your benefit. This is source-dependent. NVMe is happy with some coalescing configuration, but S3, for example, might require a lot more. S3, you can do 16-megabyte segments because it's a huge throughput with huge latency. Once you get the segment request and then have the byte ranges, you then dispatch work on them. It's a CPU scan. This work is done when you have the bytes on RAM. Then the work you dispatch is regular Rust functions like slice, projection, filter, any compute that the input query wants you to do. That was CPU. Most of the things stay the same when you are scanning to the GPU. The scan orchestration stays the same, the coalescing layer stays the same because we still need those byte ranges, but now the I/O plane has to ship these buffers all the way to the GPU and return pointers to the GPU. That's when its job is done. The compute, now instead of dispatching Rust functions, it does dispatch CUDA kernels, but everything else is the same. We have a buffer pool in the middle, and I want to explain why we have a buffer pool in the middle. This is the lifetime of a byte range through a scan from S3 to the GPU. Initially, when you request bytes from S3, it lands on the kernel, receive buffer. This is a direct memory copy, so it's not an actual copy, it's the first place that these bytes land from the network card, is this kernel, receive buffer. It immediately copies the ciphertext, still in a TLS encrypted form, into the userland, one-to-one. Then we are in our HTTP libraries land where we use Rust TLS and request. Rust TLS decrypts it into plaintext copies, that's one copy, and then it's still TCP, we want to get the HTTP body out of it, so the request layer deframes it, gets the body into its own buffer, then finally it arrives to our own Vortex land. Because these yellow buffers are all streaming, because they're multi-part GET request, we have to aggregate them because we want the segment, we want a contiguous byte range. We aggregate them, so we have to copy one more time, but that's the only copy we do. We receive these chunks as a streaming fashion, and then we accumulate them until the request is done. Immediately after this is done, we dispatch the CUDA. We do CUDA async copy and expect it to land on GPU, but unbeknownst to us, internally, CUDA does another copy. It does another copy because it requires some alignment rules from us. Basically, the buffers you can copy to the GPU from the host has to be pinned and page locked, meaning it should be outside the operating system's virtual page system, so then the copy, if the buffer gets paged out during the copy, GPU doesn't segfault and stays happy. Before diving into how we can reduce the copies, I want to mention one more thing. The memcpy cost is what's important to us. The unit is a core per gigabit of bandwidth you can sustain. It's important because this core budget is also used by the orchestrating layer in scan, and also compute if you're doing the compute on the CPU. It has the opportunity cost, because you have limited CPU, if you're just wasting it on memcopies all around, then you slow down your own bandwidth. It's like a double-edged damage, so you want to reduce this as much as possible. How can we reduce this? Initially, we start with the CUDA bounce buffer because we know the requirements. We can satisfy those requirements on our own buffer that we accumulate the HTTP buffers into. We have a pinned buffer that we use, and then we then call CUDA async copy on it. It doesn't use a bounce buffer, and this goes directly into the GPU. Further, because allocating these are expensive, we keep a buffer pool around to amortize the allocation cost. The way we reuse these buffers is when you dispatch the CUDA copy, you append the CUDA event after that CUDA stream. You append an event. Once that event fires, you know everything that comes before it is complete, so then you can safely reclaim the buffer because that means the transfer is complete, and you can reuse that same buffer for next copies in your pipeline. This reduces the cost a bit, but we need to then look to the left, to the HTTP land, to get rid of some of the copies. One thing you can do here, if you have a sufficiently high Linux version, you can use a kTLS, kernel-side TLS decryption. That pushes the decryption into the kernel, and because the kernel was immediately copying into the userland, then it doesn't need to do it anymore because now it's decrypted, and then the decrypted can be in place. The decryption can be in place in the receive buffer itself, so it saves one copy. There's one caveat that the Linux ciphers are mostly not really that up-to-date in kTLS, so they might not end up using the widest SIMD registers your machine has, but still, the memcpy saves here because we reduce one copy, offsets that deficiency, in a way, so we still keep this kTLS in this land. What else can we improve? We can improve more things on the HTTP side, but for this, we need to do bigger changes because there is no low-hanging things we can do here. If you do bigger changes, namely writing your own HTTP client, then it pays off well, because if you write your HTTP client, you control which buffers you give to kernel. If you do it, you can use the already pinned and page locked Vortex buffer you have to accumulate and deframe on the fly in the same buffer. Once you get the decrypted bytes as a stream in the multi-part GET request, you keep allocating them into the same buffer you already had to be ready to be sent to CUDA. Then when the multi-part GET request is complete, you dispatch to work, and you cycle these buffers, so with one copy, you go from S3 to GPU, and you have very little CPU overhead during the entire scan. There's one more iteration of this, but you have to get rid of TCP. If you have a very expensive and tedious setup, but really performant and cool RDMA object store in your hands, then you can use RDMA protocol, which is different from TCP, that it doesn't have to go to the kernel sockets layer. It can bypass the CPU entirely because it works as two peers connected via some sort of fiber, can be PCIe, can have some network connection, can be in between, but they can read and write from each other's memory regions. If you have this ability, then the GPU itself can dispatch the I/O request on your behalf. If it does it, it not only bypasses all CPU, it even bypasses the PCIe hub of your CPU, because these boxes come with multiple GPUs and come with many network cards. Most often, each GPU has an associated network card, so the RDMA would flow through from the GPU into the nearest network card, bypassing your CPU PCIe hub all together, so it's basically free. The CPU doesn't even know what's happening. For this, the network card also needs to be smart enough, but most boxes that come with high-end GPUs have that network card in place already. With all that, let's revert back to the video. Now, you can see there is some byte range coalescing, so we have a filter or projection. In this case, we have no filter, and we SELECT *, essentially, all columns, red, green, and blue. They all go through this coalescing layer. Then we fetch these bytes from S3, and then we cycle the byte buffer that we read them through, and they go only one copy into the GPU, and then the GPU processes them as 15 gigabits per second, 4K, 60 hertz, and then we stream it back to see what's happening. What's also cool about this, because while we have it, we can change the filters and projections. We can do, for example, this. This is not a post projection. This is actually a combination of a projection and a custom Vortex expression. I have written this custom expression called quantise, and I've written a GPU kernel for it, which is very basic. Quantise just gets the colors, each pixel value is 0 to 55, and then it snaps it to three levels, basically, depending on which level it's closest to. You get this effect of having only three columns. We also pruned the blue column out of the way, just for fun, so it's more yellowy, but also this discretized footage we have. This is, importantly, mostly handled by Vortex orchestration. You don't tie this kernel at the end yourself. You just say that I want this query, and the scan orchestration takes care of appending your kernel, and then dispatching work onto your kernel, and then you can keep the same throughput. Or you can do something like this. This is a bit jarring to watch, because the video skips all over. The way this works, remember I told you when we have this file with three columns? This file has four columns, and the fourth column is whether the frame has a cyclist or not. I've run this object recognition algorithm per frame, and then we now have this has_bike Boolean column, as the fourth column, and I'm filtering to only the frames that has a bike in it. The way this works is because each frame is huge. Each frame we have is 8 megabytes. We can afford to have a zone per frame. In the layout level, we have a zone-map that is as granular as it gets. We have one row per frame, and so we can prune each individual frame that doesn't have a bicyclist on it. Maybe what's more important to talk about here is all of these changes don't affect throughput. You can come up with an arbitrary filter. You can come up with your own projection. You can combine them, but you can keep the same throughput, and you only read the data that you need. Remember I told you the videos were 60 frames per second because I was intentionally slowing them down. Normally, when you let them loose and then write it to like a file, then we can stream 240 frames per second, because I was pacing the video so we can see it at 60 frames per second, but we can sustain 60 gigabits per second end-to-end with Vortex. What is Possible? I want to talk very briefly on what you can do with Vortex if you want to customize it. Remember I told you, you can have your own arrays and you can have your own layouts. A layout is responsible for I/O and then logical plan and then pruning and all that stuff, but it's also responsible for placement. There's nothing in Vortex file spec that enforces you to be in the same physical file. You can separate your segments into two. You can get the pruning segments and store them in Redis, and you can get all the array segments in S3. Redis has one-hundredth of the latency of S3, so you get pruning nearly immediately compared to the numbers you would get with S3, and then you get the S3 throughput on streaming the data to the end. Or, say you have this giant instance with multiple high-end GPUs but a single CPU, and then you have this GPU analytics engine that you shard the data into individual GPUs, and then you run a scan, and then dispatch the scan into individual shards. What if the query has an aggregation? Then you need to aggregate all of the results into a single GPU. You can model that aggregation as an RDMA scan, because GPUs are connected to each other with an extremely fast fiber called NVLink. It can sustain tremendous speeds. You can scan from one GPU to your central GPU to aggregate, and you can scan in terabits per second for the segregation. Takeaways That was Vortex. I wanted to talk about how it allows you to change your filter, to change your data mix and not reprocess everything over and again. I also want to mention that you can read from S3 into GPU around network card speeds. It can be 60 gigabits per second, can be more if you have a stronger network card. You can read from RDMA sources to your GPU at whatever PCIe connection you have, and that can be 500 gigabits or terabits with the newest PCIe. Questions and Answers Participant 1: What I try to understand about the video example, you seem to not keep the video in S3, but RGB frames that are completely separate, and you don't apply any video codec that would compress data, and then there would be I-frames, P-frames. You mentioned explicitly that's like 8 megabytes per frame? Onur Satici: Yes. Participant 1: Is this synthetical example of how great is Vortex, or there are really use cases to fetch attributes from S3? Onur Satici: That was a hypothetical example. Vortex is not a video codec, and it was just to demonstrate that this throughput we can sustain end-to-end, and we can visualize a scan in a video format, happens to be a video, but Vortex can be used like this. It's mostly catered towards analytical or training input data that you normally use. Participant 1: Is there a use for Vortex with compressed codecs, then? Onur Satici: You mean block compression, like zlib? Participant 1: If you happen to have a video stream, or even a JPEG, separate but compressed data, is there a value in passing it into Vortex column to get other benefits of it? Onur Satici: Yes, you can. With compressed JPEGs, you won't be able to run arbitrary compute on them, but you can model a sensor stream for a camera, for example, as a Vortex file. We support a lot of wide columns, so you can have a column that is responsible for these JPEG images. What would be cooler to have is some sort of transparent compression, so you can, for example, crop, and then get only the byte ranges in that crop, for example. The layouts allow you to do that, but you need to use the right image compression. Participant 2: Are database engines adopting this? Onur Satici: We support DataFusion, DuckDB, and Polars. We are also in the works of getting an Iceberg support for Vortex. It's in the works, yes. DuckDB and DataFusion is stable. You can use it today. Participant 3: You just commented on the Iceberg. You have some more details when we can expect it? Onur Satici: Yes. We are working with Iceberg to have a compatible official Vortex support. Iceberg is abstracting the file from it, that's backing Iceberg away from Iceberg itself, so we are iterating with them to ship this as fast as possible, but it's still in the works. Participant 3: I read about it a year ago in your blog post. Onur Satici: It's ongoing, yes. Participant 3: It's ongoing. Onur Satici: Yes. Participant 2: I had a slightly different question. I happened to bring this up when I was talking to some folks at LinkedIn, and then their response was, does your company let you use GPUs for anything other than pure ML workloads? This seems like a great speed-up for analytic data loads, but it seems companies tend to allocate their budgets for ML workloads specifically on GPUs. Onur Satici: I think it might be even better for other analytics use cases, because ML workloads, you save from reprocessing your data, but the speeds that Vortex can sustain, the consuming model, if it's a pre-training model, is really slow, so it doesn't need that much of a bandwidth. If you have some analytics work that's happening in the GPU, that would benefit a lot more from that higher throughput, I think. Participant 4: First, the infamous interoperability question, I suppose, in that I pick a columnar format, like Parquet, for instance, it gives me relatively wide interop. In a way, if I had to pitch a convincing narrative as to why I should pick an alternative columnar format that may not have the same level of interop, what would your top arguments be? Also, my second question would be, you talked about the network penalty and the CPU penalty when moving data. Isn't the network penalty almost a function of how S3 and S3 APIs work. If you had a better source, wouldn't you, in a way, avoid some of those network penalties? The CPU penalty, I understand, you're almost always going to get it anyway. Onur Satici: For interop, yes, we have official support for DuckDB, DataFusion, and Polars. We are working to integrate more. With Iceberg, I think you would unlock that the transition would be a lot easier, because Iceberg abstracts the file format away from you. That's the main idea. If you want to try Vortex, it's really easy to converge from Parquet, and just test it yourself to see. The other question was about network limits. S3, surprisingly needs a lot of tuning as well. You can change the byte request, even the order of those byte range requests that you send to S3, change a lot of the throughput that you can get from S3. Even you have to tune the network cards, you have to bind the right number of ENIs to the network card, that you have in the instance, so on and so forth, to maximize that throughput. Yes, for most of the time, when tuned right, S3 itself would not be your bottleneck in this pipeline. Participant 5: Most of the pruning stuff that you showed seemed like it was reliant on the data being sorted in the first place. Biggest problem that we've ended up having with a lot of this stuff is that lots of different users need the same data sorted in 10 different ways. Could you use something like the layouts to basically keep all the metadata that you need so that you could effectively have the same data sorted 10 different ways for people to read? I'm not sure it would be as efficient. You'd still have to pick a most efficient option. Onur Satici: Yes, it'd be an option. We mostly delegate sorting itself to the compute engine that we're using, so DataFusion or like DuckDB, that would handle it. Yes, I think it's an option. Participant 6: I have a question about the metadata we store in the layout. We talked about min, max of a particular field. How do we decide what metadata to expose? In the case of age, like the query was designed and the min, max was there. In general, how do you write Vortex files knowing what might make it better? Onur Satici: This actually is transparent to you, so you don't get to pick which zone-maps will end up being in the file. The statistics that we support, I showed just a subset of them, like anything that you can do to calculate before and as statistics when writing that you can use to prune afterwards, we will sort it. Like null count, for example, like min, max, for example. You can have even cardinality estimation that you have in your file, so you can help up with aggregations and stuff. Anything that can help with pruning, we try to integrate it. Adding a new one is really easy in Vortex. Participant 2: In one of your slides, really early on, you had the dictionary encoding, the three colors. I think you said there's like 4 bytes for the data, but then you didn't count the bytes in the dictionary itself? Onur Satici: That's right. Just the overhead was around 30 bits or so. Yes, so this is like 30 bits, and that's plus this. You can compress this further if you have the right encoding. Maybe to finish on the S3 question, actually, like maybe counterintuitively, but S3 bandwidth can surpass the NVMe bandwidth if you tune it right. For example, in those scans that I was showing, you can scan from S3 faster than you can scan from your local NVMe. The initial latency is higher, but the throughput is also higher from S3. Participant 2: I remember when Parquet first came out. This was when the big data world was trying to compete with the established MPP databases. One of the first things they did was they said, like row-major is not great, let's go to this columnar format. The next thing they said is, if we're using JDBC, we're taking what was columnar and going back to row-major. Then we're not getting any of the benefit with pipelining this through memory, through all the layers to registers. That's where Arrow came in. How does Arrow figure into this? Onur Satici: All of the compressed Vortex, like we call them the canonical encodings, for each logical type, we have a canonical encoding. They are one-to-one with Arrow, and they're zero-copy from and to Arrow. That's how we plug into DataFusion as well. DataFusion needs Arrow batches. We just decompress Vortex arrays, get the Arrow batches, and then forward with that. If you have, for example, DuckDB, DuckDB has multiple types of vectors. There's a constant vector, and we also have a constant array. It's already a constant array. There's no need to decompress and then re-encode it by DuckDB as a constant array. We pipe that through. Participant 2: Let's say I take a Parquet file and convert to Vortex. I think it had stats on how much faster. Onur Satici: Yes, I have numbers. Participant 2: I think it was before, you had something like 30x faster or something. Onur Satici: Yes, I have numbers for GPU scans, but I also have numbers for CPU scans and writes and reads and random access. This was telling that the GPU scans were 30 times faster, 100 times faster for random access. The CPU scans are around 20 times faster. CPU writes, writing a Parquet file is around five times faster than Parquet, in that ballpark. Participant 2: I think, to the other question, which is like Avro was established for a long time. I was at LinkedIn for many years, and Avro was the standard. Then it was a huge amount of data, but they converted it to Parquet because it made sense. It would also make sense, eventually, to move all Parquet to Vortex, if it's faster on CPU and GPU. Is there any size difference in the actual file? Onur Satici: Size difference? The compression ratio is very similar to Parquet. It's like 10% more, 10% less in that same ballpark. Participant 2: There's like the compression of Parquet, and then there's compression, like Parquet ZSTD, which is even further. Are you converting Parquet to Vortex? Onur Satici: ZSTD Parquet to Vortex. Participant 2: Parquet ZSTD to Vortex? Onur Satici: Yes. Participant 2: You don't need to apply ZSTD on Vortex? Onur Satici: No. Participant 2: Would there be any benefit to doing that, or would you basically not get much compression? Onur Satici: The layouts, they also have writers and readers. We have a custom layout writer that you can use called a compact writer that does zstandard for some data, but, for example, uses Pcodec for floats. If you want an even smaller file, you can tune that in, but then you would lose the computation advantages that you would get. It's up to you. Vortex is completely configurable. You can have any writer or reader and combine them as you wish. Participant 2: This is V1 or V sub-1? Onur Satici: It's V1. It's stable as of six months ago, I think. Participant 2: You're a committer, maintainer on this project. Onur Satici: Yes. Participant 2: If we look at Protobuf, and Protobuf 3 over 20-something years. Every 7 years something had changed, it became incompatible with the previous. What is your view on Vortex? One thing is if we look at GPUs, they have this virtualization layer. You code to that virtualization layer. The GPUs change very often, but the CUDA coding doesn't change at all. How do you view your format? What do you have to keep up with? Because if you're coding to that layer, versus to the GPU hardware itself, then you're standard. You have to change this to make the most use of the hardware itself. The hardware changes very fast. How would you see the backward compatibility of Vortex over time? Onur Satici: The file spec is standard, and it's intentionally minimal. We don't need to change the file spec to change most of the things that you would need a file change for other formats, for example. Like Parquet spent around 7 years incorporating f16s, because the committee, with a lot of big corporations behind it, have to agree on one form or another. Or ALP, for example, is a cool floating-point compression. It's been open for a couple of years now, and it's nowhere near to be merged. For Vortex, it's just a crate. You just write a new Rust crate, and then that's your new layout. The core part of the file would not break, because it's very simple. What you can have is you can have some new encodings that other people have written that you don't have, so you cannot read their files, but you can just add the library and then read them. Participant 2: Do you support non-Rust Serde? Onur Satici: Yes, we have an FFI binding. We even have a Java binding. Yes, any languages, you can plug in with Vortex. Participant 7: You gave an example with arrays and dictionaries. Ultimately, it's still you keep nested data structures inside a single column. You optimize them inside the column, but you still don't attempt to expand them on the shredding of nested structures, build columns, or achieve better compressions there. Is there a step improvement in Vortex versus Parquet for maps or JSON, these kinds of data structures? Onur Satici: We currently don't have that in the core, but it can be easily added as a custom layout, because shredding is just like a new layout that you would write yourself. It would treat the JSON blobs as binary, and then extract that, and then store the fields that you want as a separate column. It's a great extension point. It's just not in the core yet. Participant 7: Is it yet not in the core, or it's by design, and not what you're trying to solve? Onur Satici: We think the compression ratio and then the query patterns that, for example, in TPC-H, TPC-DS, and ClickBench, like this compression works well so far. If we need some use case that would benefit greatly from shredding, then it's not that difficult to integrate in with a new type of layout, essentially. Participant 8: I have a question regarding consistency and error correction about the Vortex file. Which mechanism are you using, and how, when you read the file, you can identify a file is corrupted, and so you need to re-download it or recreate it? Onur Satici: Currently, by default, we rely on the network stack, I think. You can add Merkle trees and parity on the footer as metadata, and you can get your layout to check them before deserializing the files, if you want. See more presentations with transcripts Recorded at: Sep 04, 2026 by Onur Satici Related Sponsors This content is in the AI, ML & Data Engineering topic Related Topics: Development Architecture & Design AI, ML & Data Engineering S3 Columnar Databases QCon London 2026 Rust AWS System Programming Amazon Database QCon Software Development Conference Transcripts NoSQL GPU Streaming Data Lake Data Pipelines Performance CUDA Machine Learning Architecture InfoQ Cloud Computing Related Editorial Popular across InfoQ Kubernetes Promotes KYAML as a Safer, More Consistent Way to Work with Manifests Airbnb Cuts Authentication Code by 60% with Server Driven Architecture OpenClaw 2.0 Releases with Simplified Setup and Collaborative Agents Google Mantis: an Agentic Vulnerability Scanning Harness for Reducing False Positives Java News Roundup: TornadoVM 6, JReleaser, LangChain4j, Java Operator SDK, JHipster, Yupiik Fusion Personality Over Skillset: How Adam Wachtel Builds Engineering Teams

来源报道 · 2 篇 · 以下链接均前往原始媒体

← 返回精选动态 原文

更多内容