
tinygrad HCQ 兼容运行时深度解析硬件命令队列、信号同步与 HCQGraph【免费下载链接】tinygradYou like pytorch? You like micrograd? You love tinygrad! ❤️项目地址: https://gitcode.com/GitHub_Trending/tiny/tinygradHCQHardware Command Queue是 tinygrad 面向高性能设备定义的一套底层运行时 API。本文以 docs/developer/hcq.md 为骨架结合 tinygrad/runtime/support/hcq.py 与 tinygrad/runtime/graph/hcq.py 的源码实现系统讲解 HCQ 的队列抽象、信号同步、分配器、程序参数状态与 HCQGraph 静态图执行机制并给出 AMD、NV 等后端的具体落地方式与测试用例。读完本文你将能够理解 tinygrad 如何绕过 HIP/CUDA 直接驱动硬件队列并具备为新型设备实现 HCQ 兼容后端的能力。Overview什么是 HCQ 兼容运行时HCQ 兼容运行时的核心特征在于与设备的交互方式。在 HCQ 中所有与设备的交互都以硬件友好的方式通过 命令队列 完成。命令被直接下发到设备绕过了 HIP、CUDA 这类运行时带来的额外开销。此外通过使用 HCQ API这些运行时还能受益于多种优化与特性包括 HCQGraph 以及内建的性能分析能力。从源码结构看HCQ 抽象集中在两个文件tinygrad/runtime/support/hcq.py定义HWQueue、HCQSignal、HCQCompiled、HCQAllocator、HCQBuffer、HCQProgram、HCQArgsState等全部基础类tinygrad/runtime/graph/hcq.py定义HCQGraph即 HCQ 设备的GraphRunner实现。在 docs/developer/developer.md 中HCQ 被定位为定义运行时的更底层 API——与设备的交互发生在更低的层级命令被直接下发到硬件队列。典型的 HCQ 后端包括 NVNVIDIA 用户态驱动 与 AMDAMD 用户态驱动此外还有 QCOM、CPU、RDMA 等实现。Command Queues硬件命令队列HWQueue要与设备交互需要创建一个HWQueue。部分方法是必需的例如timestamp以及 signal 和 wait 这类同步方法而另一些方法则取决于该队列是计算队列compute queue还是拷贝队列copy queue。以下 Python 代码在 HCQ 兼容设备上入队了一条 wait、一条 exec 和一条 signal 命令HWQueue().wait(signal_to_wait, value_to_wait) \ .exec(program, args_state, global_dims, local_dims) \ .signal(signal_to_fire, value_to_fire) \ .submit(your_device)每个运行时都应实现HWQueue类中定义的必需函数。HWQueue是一个泛型基类Generic[SignalType, HCQDeviceType, ProgramType, ArgsStateType]内部通过self._q列表累积命令字见 hcq.py并维护符号变量syms、q_sints用于后续的动态更新。通用命令所有队列必需方法签名语义timestamptimestamp(signal)入队一条时间戳命令在所有先前已入队命令完成后将当前时间记录到 signal 中signalsignal(signal, value)入队一条信号命令在所有先前操作完成后将 signal 置为给定 valuewaitwait(signal, value)入队一条等待命令暂停执行直到 signal 的值大于等于给定 value计算队列命令仅 compute queuememory_barrier()入队内存屏障命令确保各代理agent之间的内存一致性仅用于计算队列exec(prg, args_state, global_size, local_size)入队一条内核程序执行命令global_size等价于 CUDA 的 grid 尺寸local_size等价于 block 尺寸仅用于计算队列write(b, val, b64False)在前序命令完成后向缓冲区地址写入一个值默认 32 位b64True时写 64 位默认实现抛出NotImplementedErrorpoll_bit(b, val, mask)入队轮询命令直到(mem[b] mask) val才继续执行val必须为 0 或mask即检查掩码位是否全部清零或全部置位默认实现抛出NotImplementedError。拷贝队列命令仅 copy queuecopy(dest, src, copy_size)入队一条数据传输命令仅用于拷贝队列。以 AMD 的AMDCopyQueue和 NV 的NVCopyQueue为例它们分别通过 SDMA 引擎和NVC6B5_LAUNCH_DMA方法实现硬件拷贝。submit 与 bindsubmit(dev, var_valsNone)将命令队列提交给指定设备执行若提供了var_vals会先调用_apply_var_vals将符号变量解析为具体值见 hcq.pybind(dev)可选方法将队列与特定设备关联以进行优化执行。实现该方法可以消除将队列复制到设备 ring 中的开销从而提升性能见 hcq.py。AMD 的AMDCopyQueue通过encode_ring系列方法将队列命令编码进硬件 ring 缓冲区正是对 bind 的典型落地。符号变量的惰性更新HWQueue允许把VariableUOp.variable直接入队q()方法在遇到UOp值时记录其符号索引并用占位值0xbadc0ded占位见 hcq.py。提交时_apply_var_vals只重写发生变化的位置这正是后面 HCQGraph 做到每次运行只更新队列必要部分的基础。HCQ 兼容设备HCQCompiledHCQCompiled类定义了 HCQ 兼容设备的 API它是一个抽象基类设备特定实现应继承并实现它见 hcq.py。其构造参数包括device设备名allocatorHCQ 兼容分配器compilers渲染器Renderer列表runtime程序类型signal_t信号类型HCQSignal子类comp_queue_t/copy_queue_t计算/拷贝队列的工厂函数kernargs_size内核参数缓冲区大小默认16 2016 MiBsigalloc_size信号页分配大小默认0x1000can_recover设备能否从故障或超时中恢复。初始化时HCQCompiled会注册到HCQCompiled.peer_groups[peer_group]同一 peer group 内的设备共享统一的虚拟地址空间可互相映射内存并创建内核参数缓冲区kernargs_buf与配套的BumpAllocator见 hcq.py。CPU 设备会被追加到HCQCompiled.cpu_devices供多设备同步时统一处理。后端实现示例AMDDeviceAMDSignal(HCQSignal)、AMDComputeQueue(HWQueue)、AMDCopyQueue(HWQueue)、AMDAllocator(HCQAllocator)NVDeviceNVQueue(HWQueue)、NVAllocator(HCQAllocator)QCOMDeviceQCOMComputeQueue(HWQueue)、QCOMAllocator(HCQAllocator)CPUDeviceCPUAllocator(HCQAllocator)RDMADeviceRDMACopyQueue(HWQueue)、RDMAAllocator(HCQAllocatorBase)。Signals信号与时间戳信号Signal是 HCQ 兼容设备上用于同步和计时的设备相关结构。它们被设计为在同一个信号中同时记录value和timestamp。HCQ 兼容后端应以HCQSignal为基类实现自己的信号见 hcq.py。HCQSignal的关键成员成员说明value信号值存储在缓冲区偏移 0 处的 8 字节fmtQtimestamp时间戳存储在偏移 8 处的 8 字节除以timestamp_divider后以微秒返回wait(value, timeoutNone)阻塞等待信号值大于等于value超时默认取环境变量HCQDEV_WAIT_TIMEOUT_MS默认 30000 ms30 秒超时抛RuntimeError信号由HCQCompiled.new_signal()分配从 peer group 级的signal_pool中取出 16 字节的信号槽每页sigalloc_size字节可切出多个槽并映射到同 peer group 内所有设备见 hcq.py。以下代码演示信号的使用方式signal your_device.new_signal(value0) HWQueue().timestamp(signal) \ .signal(signal, value_to_fire) \ .submit(your_device) signal.wait(value_to_fire) signaled_value signal.value # should be the same as value_to_fire timestamp signal.timestamp注意wait循环中带有进度检测只要信号值发生变化就重置超时计时器避免长任务被误判超时见 hcq.py。同步信号Synchronization signals每个 HCQ 兼容设备都必须为全局同步分配两个信号并在初始化时传给HCQCompiled基类self.timeline_signal活跃的时间线信号self._shadow_timeline_signal影子时间线信号用于处理信号值溢出问题。二者在 hcq.py 中通过new_signal(value0, is_timelineTrue)创建。当timeline_value超过1 31时synchronize()会调用_wrap_timeline_signal()将影子信号切换为活跃信号并重置计数见 hcq.py从而避免 32 位信号值回绕导致的同步错误。该路径在 test/device/test_hcq.py 的test_timeline_signal_rollover中被专门验证。HCQ 兼容分配器HCQAllocator 与 HCQBufferHCQAllocator基类借助命令队列抽象简化了分配器逻辑它高效地处理拷贝与传输操作后端只需实现_alloc与_free见 hcq.py。HCQAllocatorBase在构造时预分配一批主机侧批量缓冲区self.b默认batch_cnt32个、每个batch_size220字节并配合b_timeline/b_next做环形复用。其核心方法包括_copyin(dest, src)主机 → 设备拷贝。若设备没有拷贝队列则退化为同步memmove否则通过拷贝队列分批copy并打时间线信号见 hcq.py_copyout(dest, src)设备 → 主机拷贝先synchronize()保证数据就绪再经拷贝队列回读_transfer(dest, src, sz, src_dev, dest_dev)设备间传输。同 peer group 直接映射后由src_dev的拷贝队列执行跨 peer group 则路由到src_dev.rdma_dev()走 RDMA 路径见 hcq.pycopy_from_disk(dest, src, size)磁盘 → 设备的高效分片拷贝通过_copyout_sharded与 ioring 机制实现_map(buf)将缓冲区映射到本设备统一地址空间下映射一次后对所有同 peer group 设备有效。HCQ Allocator Result ProtocolHCQBuffer后端在返回分配结果时必须遵循HCQBuffer协议。HCQBuffer是一个轻量描述符见 hcq.py成员说明va_addr缓冲区的虚拟地址可为sint即支持符号变量地址size字节大小meta/_base/owner元数据、基缓冲区用于 offset 视图与所属设备offset(offset, size)返回子视图缓冲区cpu_view()返回 CPU 可访问的MMIOInterface视图mappings/mapped_devs其他设备的映射表与已映射设备列表HCQSignal.value/timestamp正是通过base_buf.cpu_view()读写va_addr与va_addr 8处的内容实现的。HCQ 兼容程序HCQProgram 与 HCQArgsStateHCQProgram是面向 HCQ 设备的程序基类为处理不同的参数布局即HCQArgsState提供了灵活框架见 hcq.py。fill_kernargs(bufs, vals, kernargsNone)将缓冲区地址与标量值写入内核参数区返回HCQArgsState未指定kernargs时从设备的kernargs_buf中经kernargs_offset_allocatorBumpAllocator可回绕分配kernargs_alloc_size字节__call__(*bufs, global_size, local_size, vals, wait, timeout)便捷执行入口——自动构建一条完整队列wait(timeline_signal, timeline_value - 1)→memory_barrier()→exec(...)→signal(timeline_signal, next_timeline())→submit(dev)waitTrue时调用dev.synchronize()并返回内核执行耗时微秒。Arguments State参数状态HCQArgsState是管理 HCQ 程序参数状态的基类后端应创建其子类来管理给定程序的参数见 hcq.py。它持有buf内核参数缓冲区、prg、bufs、vals并通过bind_data记录符号值 → 内存地址的绑定关系供HWQueue.bind_args_state批量绑定。内置的CLikeArgsState展示了典型布局可选prefix前缀如常量缓冲数据随后依次写入各缓冲区地址64 位与标量参数依据TinyELF.iter_sig从程序签名推导的类型与偏移。生命周期约定HCQArgsState被传给HWQueue.exec后保证在同一个队列调用HWQueue.submit之前不会被释放见原文档 Arguments State 一节。这一约定使exec可以安全地延迟到提交时才真正消费参数。Synchronization全局时间线同步HCQ 兼容设备使用一个全局时间线信号global timeline signal来同步所有操作从而确保任务在设备上的正确顺序与完成见 hcq.py。按约定self.timeline_value指向下一个要 signal 的值。因此要等待设备上所有先前操作完成应等待timeline_value - 1。以下代码演示了使用信号将执行同步到设备上其他操作的典型用法HWQueue().wait(your_device.timeline_signal, your_device.timeline_value - 1) \ .exec(...) .signal(your_device.timeline_signal, your_device.next_timeline()) \ .submit(your_device) # Optionally wait for execution your_device.timeline_signal.wait(your_device.timeline_value - 1)其中next_timeline()递增timeline_value并返回旧值见 hcq.py。synchronize(timeout)则封装了完整的等待逻辑等待timeline_value - 1、超时记录error_state并可触发on_device_hang恢复回调、必要时回绕时间线信号、并在PROFILE开启时输出性能分析事件见 hcq.py。HCQGraph静态图执行HCQGraph是 HCQ 的核心特性之一它为 HCQ 兼容设备实现了GraphRunner见 tinygrad/runtime/graph/hcq.py。HCQGraph为每个设备构建静态的HWQueue覆盖图中全部操作为了优化入队时间每次运行时只通过符号变量更新队列的必要部分而避免整体重建。构建期静态队列与依赖调度构造时__init__完成以下工作设备归类收集图中涉及的 HCQ 设备CPU 设备恒排最后把每个CALL的输入缓冲区替换为符号变量UOp.variable得到可静态化的hcq_bufs内核参数预分配按每个设备汇总kernargs_alloc_size并一次性分配kernargs_bufs随后为每个 jit item 调用fill_kernargs生成初始ji_args队列创建每个设备一个计算队列comp_queues拷贝队列copy_queues按需懒创建数量由环境变量HCQ_NUM_SDMA控制默认ALL2ALL 1时为min(设备数, 8)否则为 1见 hcq.py依赖解析_resolve_deps依据缓冲区读写关系生成 wait 依赖并做去冗优化——跳过那些已知与当前队列同步过的队列信号queue_access记录同时对 AMD/QCOM 等设备移除对自身输出信号的自依赖见 hcq.py跨设备与跨 peer group 同步计算队列先memory_barrier().wait(虚拟时间线信号, 虚拟时间线值)等待外部操作再等待 kickoff 信号拷贝队列等待 kickoff 信号后才开始搬运跨 peer group 的传输走RDMACopyQueue用符号变量维护 doorbell head 计数见 hcq.pyPROFILE 支持开启PROFILE时每个 jit item 分配一对开始/结束信号并把依赖关系编码为ProfileGraphEntry形成整图的性能剖析数据见 hcq.py。运行期符号变量驱动的最小更新每次调用__call__(input_uops, var_vals, wait)时见 hcq.py递增kickoff_value等待上次运行的时间线信号完成组装hcq_var_vals包含 kickoff 变量、输入缓冲区地址通过input_replace_to_var映射、各设备虚拟时间线信号地址与值、RDMA doorbell head 等对每条队列调用submit(dev, var_vals)——HWQueue._apply_var_vals仅重写发生变化的符号位置重置队列信号值并置位 kickoff 信号一次性发射整张图waitTrue时等待所有设备的时间线信号并返回总耗时。因为静态队列结构不变、每次只 patch 变化的值所以省去了每次运行重新构建命令的开销。测试 test/unit/test_hcq_graph.py 中的test_supports_uop验证了HCQGraph.supports_uop的门槛逻辑所有设备必须是HCQCompiled子类、CPU 批处理要求时间线信号映射为MMIOInterface、跨 peer group 需要 RDMA 设备。bind API 的进一步优化如原文档所述队列可以可选地实现bindAPI从而进一步消除将队列复制进设备 ring 的开销。在HCQGraph构建末尾每个设备的计算队列与拷贝队列都会被bind(dev)见 hcq.pytest/device/test_hcq.py的test_bind则验证了 bind 后的队列配合符号变量仍能正确提交执行见 test/device/test_hcq.py。在测试与实践中验证 HCQ仓库提供了两套 HCQ 相关测试可作为学习与验证的入口test/device/test_hcq.py面向真实/模拟 HCQ 设备的端到端测试覆盖信号、wait、exec、copy、bind、多设备 P2P、时间线回绕、内存屏障、性能计时内核耗时与拷贝带宽等场景。运行方式示例python -m pytest test/device/test_hcq.py -k test_signal or test_copy需 HCQ 设备MOCKGPU 环境下同样可跑路径验证test/unit/test_hcq_graph.pyHCQGraph单元级门槛测试。关于运行时配置等待信号超时可通过环境变量HCQDEV_WAIT_TIMEOUT_MS调整默认 30000 ms拷贝队列数量由HCQ_NUM_SDMA控制旧的环境变量HCQ_VISIBLE_DEVICES已被废弃应改用DEV目标语法见 hcq.py。跨设备通信策略ALL2ALL/RING则由 tinygrad/helpers.py 中的 ContextVar 控制。总结HCQ 兼容运行时是 tinygrad 在用户态直接驱动硬件路线上的核心抽象HWQueue提供硬件友好的命令入队接口信号同步、计算、拷贝、bindHCQSignal在统一的地址空间中同时承载值与时间戳HCQCompiled/HCQAllocator/HCQProgram/HCQArgsState组成设备、内存与程序的完整抽象层全局时间线信号与影子信号保障跨操作、跨设备的有序与溢出安全HCQGraph则将整张计算图静态化为可增量更新的硬件队列最大限度降低每次运行的入队开销。理解这套机制是阅读 AMD/NV 等用户态驱动源码、乃至为新型加速器编写 tinygrad 后端的基础。【免费下载链接】tinygradYou like pytorch? You like micrograd? You love tinygrad! ❤️项目地址: https://gitcode.com/GitHub_Trending/tiny/tinygrad创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考