news 2026/10/23 13:48:48

AnyPS5:Linux下PS5 GPU硬件能力重映射工具链解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AnyPS5:Linux下PS5 GPU硬件能力重映射工具链解析

1. 项目概述:AnyPS5不是PS5模拟器,而是一套面向Linux开发者的PS5硬件能力复用工具链

AnyPS5这个标题在当前技术社区里容易引发第一层误解——很多人会下意识联想到“PS5模拟器”或者“在PC上跑PS5游戏”。但实际完全不是这么回事。我从去年底开始接触这个项目,当时在GitHub上看到它被归类在“Linux驱动开发”和“嵌入式GPU工具”两个标签下,点进去才发现,它根本不是面向游戏玩家的,而是为Linux内核开发者、图形栈工程师、以及需要深度调用RDNA3架构GPU能力的AI推理团队准备的一套底层能力桥接方案。核心关键词“relinker”就是破题钥匙:它不模拟PS5系统,而是把PS5主机里那颗定制RDNA3 GPU(代号“Oberon”)的固件接口、内存映射规则、DMA通道调度逻辑,通过一套可重链接的内核模块+用户态库,重新适配到标准Linux发行版中。换句话说,AnyPS5的本质是“让普通Linux机器能像PS5主机一样调度那块GPU”,而不是“让Linux跑PS5游戏”。

这解释了为什么所有热词都绕不开Linux、relinker、Windows对比——因为项目默认只支持Linux主线内核5.15+,Windows支持仅停留在实验性Wine层封装,且明确声明“不提供DirectX 12 Ultimate兼容层”。那些搜索“ps5支持mesh shader吗”的用户,其实真正想问的是“AnyPS5能否暴露PS5 GPU原生的Mesh Shader管线控制权”,答案是肯定的,但前提是你的Linux内核已打上AMD GPU DRM补丁,并且你得自己写GLSL扩展着色器来绑定Mesh Task Shader入口。至于“linux镜像安装”“免费linux网站大全”这类泛搜索,恰恰反向印证了当前用户群体的构成:大量刚从树莓派或旧笔记本转向嵌入式GPU开发的新手,误以为AnyPS5是开箱即用的发行版,结果卡在内核模块签名验证环节超过3小时。我实测过Ubuntu 22.04、Debian 12和Arch Linux三种环境,只有Arch因滚动更新机制能直接启用最新drm-next分支,其他两个发行版必须手动编译内核模块,这个细节后面会拆解。

适合谁参考?三类人最值得投入时间:一是正在做边缘AI推理设备选型的嵌入式工程师,AnyPS5能让一块二手PS5主板变身低功耗高吞吐GPU服务器;二是Linux图形驱动贡献者,它的relinker机制提供了比AMDGPU开源驱动更细粒度的寄存器级控制;三是高校计算机体系结构课程设计者,它把GPU微架构调度逻辑变成了可调试的C代码。如果你只是想用PS5手柄玩PC游戏,装个xpad驱动就够了,AnyPS5对你而言反而增加复杂度。

2. 核心设计思路:为什么放弃模拟,选择“硬件能力重映射”这条路?

AnyPS5的架构选择背后,藏着对PS5硬件特性的深刻理解。PS5的GPU不是简单叠加在RDNA2上的升级版,它有三个独有设计:第一是定制化的Infinity Cache控制器,缓存行大小从64B强制扩展到256B,且支持跨CCX的原子操作;第二是GPU内部集成的Secure Processor(SP),所有DMA传输必须经SP鉴权,否则触发硬复位;第三是PCIe Gen4 x16通道被拆分为两组x8,其中一组专供SSD直连GPU,形成“GPU-SSD内存池”。这些特性决定了传统QEMU模拟路线走不通——模拟器无法精确建模SP鉴权时序,更无法复现PCIe拓扑级的带宽隔离。所以AnyPS5团队干脆放弃模拟,转而采用“硬件能力重映射”策略:把PS5主板当做一个特殊PCIe设备,用Linux内核的PCIe AER(Advanced Error Reporting)机制捕获其硬件错误码,再通过relinker动态重写GPU固件加载地址,把原本指向PS5定制BootROM的指针,重定向到Linux内核预留的DMA缓冲区。

这个设计的关键在于relinker的双重作用。它既是链接器(Linker),负责解析PS5固件ELF文件中的符号表;又是重定位器(Relinker),在运行时修改固件代码段里的绝对地址跳转指令。举个具体例子:PS5固件里有一条jmp 0x12345678指令,指向SP鉴权模块入口,relinker会实时扫描该指令,将其替换为jmp 0x87654321——后者是Linux内核中模拟SP鉴权的函数地址。这种操作比KVM虚拟化更底层,也更危险,一旦重定位出错,整块GPU会锁死,必须物理断电重启。正因如此,项目文档反复强调“首次运行必须连接JTAG调试器”,这不是故弄玄虚,而是因为relinker的日志输出全靠JTAG串口,USB调试模式会被SP拦截。

为什么不用Windows?这里涉及一个关键事实:Windows的GPU驱动模型(WDDM)强制要求所有GPU内存分配走DXGI管理器,而PS5的SP鉴权机制要求内存页必须由内核直接映射。WDDM的抽象层恰好切断了这条通路,导致Windows下即使能加载固件,DMA传输也会在SP校验阶段失败。Linux的DRM/KMS框架则允许驱动绕过MMU直接操作IOMMU页表,这才给了relinker操作空间。这也是所有热词里“Windows”出现频率高却无实质进展的原因——不是开发者不想做,而是Windows内核架构天然排斥这种操作。

3. 核心组件拆解:relinker、Oberon驱动、SP模拟器三者如何协同工作?

AnyPS5的代码仓库看似简单,只有三个主目录:relinker/、drivers/gpu/drm/oberon/、sp-sim/,但每个目录都藏着需要逐行啃读的硬核细节。我花两周时间把它们全部编译调试通,现在来拆解这三者的协作逻辑。

3.1 relinker:不只是链接器,更是运行时固件手术刀

relinker的核心文件是relinker.c,它的工作流程分三步:固件解析、符号重定位、运行时注入。第一步解析PS5固件(通常是gpu_fw.bin)时,它不依赖标准libelf,而是用自研的elf_parser模块,原因在于PS5固件使用了非标准的ELF节区命名——.text.sp(Secure Processor代码段)和.data.gpu(GPU专用数据段)不会被通用解析器识别。elf_parser会强制扫描所有节区头,找到这两个特殊节区后,再提取其中的重定位表(Relocation Table)。这里有个坑:PS5固件的重定位表用的是ARM64的R_AARCH64_JUMP_SLOT类型,但relinker默认按x86_64处理,必须在编译时加-DARCH=arm64参数,否则重定位地址全错。

第二步符号重定位才是精髓。relinker不是简单替换地址,而是构建了一个“符号映射图谱”。比如固件里调用sp_auth_check()函数,这个符号在PS5 BootROM里地址是0x100000,但在Linux里我们要把它映射到sp_sim_auth_check(),地址是0x80000000。relinker会生成一个映射文件symbol_map.json,记录所有此类对应关系。这个文件后续会被Oberon驱动读取,用于动态patch固件代码。特别注意:映射图谱必须包含所有SP相关符号,漏掉任何一个(比如sp_dma_lock()),GPU启动时就会卡在DMA初始化阶段。

第三步运行时注入通过/dev/oberon_relink字符设备实现。当你执行echo "load" > /dev/oberon_relink时,内核模块会触发relinker_inject()函数,把重定位后的固件代码拷贝到GPU的VRAM起始地址(固定为0x10000000),然后跳转执行。这里有个安全机制:relinker会先校验固件SHA256哈希值,只允许白名单内的哈希值通过,防止恶意固件注入。白名单存放在/lib/firmware/oberon/whitelist.sha256,首次安装时需手动添加你下载的固件哈希。

3.2 Oberon驱动:DRM框架下的GPU资源管家

drivers/gpu/drm/oberon/目录下的驱动代码,本质是AMDGPU驱动的精简定制版。它不处理显示输出(PS5没有HDMI输出需求),专注三件事:内存管理、命令提交、中断处理。内存管理模块oberon_gem.c最值得深挖——它绕过了Linux标准的GEM(Graphics Execution Manager)框架,直接操作IOMMU页表。原因在于PS5的SP要求DMA地址必须是连续物理页,而GEM的内存池可能产生碎片。Oberon驱动为此实现了oberon_iommu_alloc()函数,每次申请内存时,会调用alloc_pages()获取连续页,再用iommu_map()映射到GPU可见地址空间。实测发现,这个函数在4GB内存以下的机器上成功率不足60%,因为连续页申请失败率太高,解决方案是启动时加mem=3G内核参数预留内存。

命令提交模块oberon_ring.c暴露了PS5 GPU的独有特性。它定义了两种ring buffer:gfx_ring(图形计算)和sp_ring(Secure Processor通信)。前者对接标准DRM ioctl,后者则通过ioctl(OBERON_SP_CMD)专用接口。当你提交一个Mesh Shader任务时,Oberon驱动会把任务描述符写入sp_ring,由SP校验后才转发给gfx_ring执行。这个双ring设计正是PS5硬件安全模型的软件映射,也是AnyPS5能支持Mesh Shader的根本原因——SP Ring确保了Shader代码未经鉴权不得执行。

3.3 SP模拟器:用C语言重写的硬件安全协处理器

sp-sim/目录是整个项目的灵魂。它用纯C实现了PS5 Secure Processor的全部功能,包括:密钥存储(AES-256)、DMA地址校验、内存访问权限控制。SP模拟器不模拟硬件电路,而是模拟其行为逻辑。核心文件sp_core.c里,sp_auth_check()函数会检查传入的DMA地址是否在白名单范围内(白名单由/etc/oberon/sp_whitelist.conf配置),并验证地址对应的内存页是否标记为SP_MEM_TRUSTED。这个标记由Oberon驱动在分配内存时设置,形成闭环。

SP模拟器最关键的创新是“时序敏感校验”。PS5硬件中,SP校验必须在100ns内完成,否则GPU触发超时复位。Linux内核无法保证如此严格的实时性,所以SP模拟器采用了“预校验+异步确认”机制:当Oberon驱动准备DMA传输时,先调用sp_precheck()快速验证地址合法性(耗时<10ns),然后把完整校验任务放入workqueue异步执行。如果异步校验失败,SP模拟器会发送IPI中断通知GPU停止当前任务。这个设计牺牲了部分安全性(存在100ns窗口期),但换来了可用性,实测在Intel i7-11800H上,99.9%的DMA请求都能在超时前完成预校验。

提示:SP模拟器的密钥管理依赖Linux内核密钥环(keyring)。首次运行前必须执行keyctl add user sp_key "your-secret-key" @u,否则所有SP校验都会返回失败。这个步骤文档里没写清楚,是我踩坑后翻内核日志才发现的。

4. 实操全流程:从硬件准备到Mesh Shader验证的七步落地

部署AnyPS5不是装个包那么简单,它要求你对Linux内核、PCIe协议、GPU架构都有基础认知。我整理了一套经过三次完整验证的实操流程,每一步都标注了常见失败点和绕过方案。

4.1 硬件准备:PS5主板拆解与PCIe转接卡选型

第一步永远是硬件。你需要一台PS5主机(建议CUH-ZCT2型号,其Oberon GPU固件最稳定),拆开后取出主板。重点检查GPU芯片右下角的丝印:必须是“OBERON 1A”或“OBERON 1B”,早期CUH-ZCT1的“OBERON 1”固件不兼容AnyPS5。拆解视频网上很多,但要注意:PS5主板GPU散热器用的是导热胶而非硅脂,强行撕下会损坏GPU焊点,正确做法是用热风枪均匀加热散热器四周至80℃,再用塑料撬棒轻撬。

GPU主板需要转接到PC上,这里PCIe转接卡是最大雷区。市面上90%的“PCIe延长卡”用的是PLX桥片,但PS5 GPU要求PCIe链路训练时长不超过20ms,PLX桥片通常要40ms以上。实测唯一可用的是“ASUS Hyper M.2 X16 Card”,它用ASM1083桥片,链路训练仅12ms。插卡时务必关闭PC机箱风扇——PS5 GPU散热器设计风道与PC机箱冲突,风扇直吹会导致GPU温度传感器误报,触发降频。我用红外测温仪实测,机箱风扇开启时GPU核心温度比关闭时高18℃,直接导致relinker注入失败。

4.2 内核编译:打补丁、关签名、配CONFIG

AnyPS5要求内核版本≥5.15,但官方补丁只适配5.19。我推荐用Linux 6.1 LTS,它对RDNA3支持最完善。编译前必须打三个补丁:oberon-drm.patch(DRM驱动)、relinker-kapi.patch(内核API扩展)、sp-sim-kapi.patch(密钥环接口)。补丁顺序不能错,否则编译报错。特别注意:relinker-kapi.patch会修改include/linux/module.h,添加MODULE_LICENSE("GPL v3")声明,这是为了绕过内核模块签名强制检查。如果你的发行版启用了Secure Boot,必须先禁用,命令是mokutil --disable-validation,否则insmod oberon.ko会返回Invalid module format。

内核配置最关键的是CONFIG_DRM_OBERON=y和CONFIG_OBERON_RELINKER=m。前者编译进内核,后者编译为模块。很多人忽略CONFIG_IOMMU_SUPPORT=y,导致Oberon驱动无法分配连续内存。编译完成后,别急着make install,先检查生成的vmlinux符号表:nm vmlinux | grep oberon,必须看到oberon_sp_auth_check等符号,否则补丁没生效。

4.3 固件提取与校验:从PS5系统镜像抠出GPU固件

PS5系统镜像(PS5UPDATE-*.pup)不是普通文件,它是Sony自定义的PUP格式,包含多层加密。提取GPU固件需要三步:首先用pup-decrypt.py解密PUP文件(密钥从PS5主板SPI Flash里读取,需JTAG),得到os0.pkg;然后用pkg-extract.py解包os0.pkg,找到/system/common/lib/libgpu_firmware.so;最后用objdump -d libgpu_firmware.so | grep -A 20 "sp_auth_check"定位固件起始地址,用dd命令截取二进制段。我实测发现,不同PS5固件版本的GPU固件偏移量不同,CUH-ZCT2 22.01版固件偏移是0x1a2c00,而22.02版是0x1a3100,必须用hexdump -C手动确认。

固件校验有两个层面:一是SHA256哈希,必须和whitelist.sha256匹配;二是CRC32校验,relinker会在注入前自动计算。如果CRC32失败,说明固件被截断,常见原因是dd命令的bs参数设得太小。正确命令是:dd if=libgpu_firmware.so of=gpu_fw.bin bs=1M skip=17 count=2(具体数值依版本而定)。

4.4 模块加载与relinker注入:四步安全启动流程

加载顺序严格遵循:sp_sim.ko→oberon.ko→relinker.ko→relinker inject。第一步insmod sp_sim.ko会创建/dev/sp_sim设备节点,此时执行keyctl add user sp_key "mykey" @u注入密钥。第二步insmod oberon.ko,成功后dmesg | tail应看到[OBERON] GPU detected: OBERON 1B。第三步insmod relinker.ko,它会注册/dev/oberon_relink设备。第四步才是关键:echo "load" > /dev/oberon_relink。

这一步失败率最高,90%的问题出在SP校验。dmesg里如果看到SP auth failed: invalid addr 0x12345678,说明sp_whitelist.conf里没添加该地址。正确配置格式是:0x10000000-0x1fffffff(VRAM范围)、0x80000000-0x8000ffff(SP模拟器代码段)。注意地址必须是十六进制,且用短横线连接起止地址,逗号分隔多个范围。

4.5 Mesh Shader验证:用glslang编译测试着色器

验证GPU是否真能跑Mesh Shader,不能只看dmesg,必须实测。我用Khronos官方glslang工具链编译了一个最小Mesh Shader:

#version 450 #extension GL_EXT_mesh_shader : require layout(local_size_x = 32) in; layout(triangles) out; layout(max_vertices = 3, max_primitives = 1) out; void main() { gl_MeshVerticesEXT[0].position = vec4(-0.5, -0.5, 0.0, 1.0); gl_MeshVerticesEXT[1].position = vec4(0.5, -0.5, 0.0, 1.0); gl_MeshVerticesEXT[2].position = vec4(0.0, 0.5, 0.0, 1.0); gl_PrimitiveIndicesEXT[0] = 0; gl_PrimitiveIndicesEXT[1] = 1; gl_PrimitiveIndicesEXT[2] = 2; gl_PrimitiveCountEXT = 1; }

编译命令:glslangValidator -V -e main -o mesh.spv mesh.glsl。生成的mesh.spv用vktrace工具提交到Oberon驱动,dmesg里出现[OBERON] Mesh task executed: 1 primitives即表示成功。注意:必须用Vulkan 1.3+,OpenGL不支持Mesh Shader扩展。

4.6 性能调优:IOMMU页表优化与DMA带宽释放

默认配置下,AnyPS5的DMA带宽只有PS5原生的60%。瓶颈在IOMMU页表刷新延迟。解决方案是修改/sys/kernel/iommu_groups/*/devices/*/iommu/io_page_table参数,把pt_cache_mode从auto改为direct。这个参数在内核文档里几乎找不到,是我通过perf record -e iommu:io_page_fault追踪发现的。改完后,dd if=/dev/zero of=/dev/oberon_dma bs=1M count=1000的吞吐量从800MB/s提升到1.3GB/s。

另一个调优点是GPU内存分配策略。Oberon驱动默认用GFP_KERNEL标志分配内存,这会导致内存压缩等待。改成GFP_DMA32后,连续页分配成功率从60%升到95%。修改位置在oberon_iommu_alloc()函数里,把alloc_pages(GFP_KERNEL, order)换成alloc_pages(GFP_DMA32, order)。

4.7 故障回滚:安全卸载与硬件保护机制

AnyPS5设计了完整的故障回滚机制。如果relinker注入失败,执行echo "reset" > /dev/oberon_relink会触发GPU硬复位。但更安全的做法是先卸载所有模块:rmmod relinker→rmmod oberon→rmmod sp_sim。注意顺序不能反,否则rmmod oberon会卡住,因为SP模拟器还在占用GPU资源。

硬件保护方面,PS5主板有独立的EC(Embedded Controller)芯片,当GPU温度超过95℃时,EC会强制切断GPU供电。AnyPS5的oberon_hwmon.c模块会监控EC温度传感器,一旦读到temp1_input: 94000(单位毫摄氏度),立即降低GPU频率。这个阈值可以调整,写入/sys/class/hwmon/hwmon*/device/temp1_max即可,但建议不低于90℃,否则频繁降频影响性能。

5. 常见问题排查:从dmesg日志到硬件信号抓取的实战指南

部署AnyPS5过程中,90%的问题都能通过dmesg定位,但剩下10%必须深入硬件层。我把三年来积累的排查经验整理成速查表,按现象分类,附带真实日志片段和解决动作。

现象dmesg典型日志根本原因解决动作
relinker: ELF parse failed: unknown section .text.sp[ 12.345] relinker: parsing gpu_fw.bin...
[ 12.346] relinker: error: section .text.sp not found
固件版本不匹配,CUH-ZCT1固件无SP代码段更换CUH-ZCT2主板,或用strings gpu_fw.bin | grep -A5 "sp_auth"确认SP段存在
oberon: DMA alloc failed: no contiguous pages[ 15.678] oberon: trying to alloc 4096 pages
[ 15.679] oberon: alloc_pages returned NULL
内存碎片化严重,连续页申请失败启动时加mem=3G参数,或用echo 1 > /proc/sys/vm/compact_memory触发内存整理
sp_sim: auth check timeout[ 18.123] sp_sim: precheck passed for 0x10000000
[ 18.124] sp_sim: async check timeout (100ns)
SP模拟器workqueue负载过高降低/proc/sys/kernel/nr_hugepages值,释放CPU资源给workqueue
oberon: GPU reset triggered by EC[ 22.456] oberon: EC temp read: 96000
[ 22.457] oberon: forcing GPU reset
散热不良,EC主动保护清理GPU散热器灰尘,更换导热胶,或外接12V风扇直吹散热鳍片
relinker: invalid signature on firmware[ 25.789] relinker: sha256 mismatch: expected xxx, got yyy固件被截断或损坏用sha256sum gpu_fw.bin比对官网哈希,重新提取固件

注意:当遇到oberon: PCIe link down时,不要立刻怀疑硬件。PS5 GPU的PCIe链路训练有特殊握手协议,PC主板BIOS可能不兼容。解决方案是进入BIOS,关闭Above 4G Decoding和Resizable BAR选项,这两个功能会干扰PS5 GPU的地址空间映射。

最棘手的问题是“无声失败”:dmesg没有任何错误,但/dev/oberon_relink设备节点不出现。这通常意味着relinker模块加载失败,但内核没报错。此时要用cat /proc/modules \| grep relinker确认模块是否真在内存中,再用lsmod \| grep -A10 relinker查看依赖模块状态。我发现80%的案例是sp_sim.ko没加载,因为密钥没注入,keyctl show命令会显示空列表。

硬件层排查必须用逻辑分析仪。PS5 GPU的PCIe时钟信号(REFCLK)频率是100MHz,但某些PC主板提供的是125MHz,导致链路训练失败。用Saleae Logic Pro 16抓取REFCLK信号,如果周期不是10ns(100MHz),就必须更换主板或使用时钟发生器模块。

最后分享一个独家技巧:AnyPS5的relinker支持调试模式。编译时加-DDEBUG_RELINKER,然后echo "debug" > /dev/oberon_relink,它会把每条重定位指令的原始地址、目标地址、修改字节写入/var/log/oberon_relink.log。这个日志能精准定位固件解析错误,比dmesg详细十倍。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 3:44:15

锐捷RGNOS接口配置实战:VLAN划分与链路聚合避坑指南

简介&#xff1a;《锐捷网络RGNOS-接口配置指南》是一份面向网络工程师、运维人员及网络初学者的接口配置手册&#xff0c;聚焦锐捷RGNOS系统下接口相关知识点&#xff0c;帮助读者理清物理接口、逻辑接口等类型差异&#xff0c;掌握从基础到高级的配置方法。资源为单个PDF文件…

作者头像 李华
网站建设 2026/10/23 13:48:44

信息学奥赛买笔问题:贪心策略与分支结构详解

2059&#xff1a;【例3.11】买笔&#xff0c;这道题我相信学过信息学奥赛的同学都不陌生。它是《信息学奥赛一本通》第三章选择结构部分的经典例题&#xff0c;题目讲的是买笔这件事——钢笔5元一支、铅笔2元一支、圆珠笔1元一支&#xff0c;要求三种笔都至少买一支&#xff0c…

作者头像 李华
网站建设 2026/10/22 14:11:57

Windows 10驱动签名强制机制详解与安全绕过实践

简介&#xff1a;本资源是一份面向Windows 10系统管理员、驱动开发者及IT运维人员的实操指南&#xff0c;聚焦解决因驱动程序未签名导致无法安装的典型问题&#xff0c;提供禁用强制签名的完整、可复现的操作路径。文档以图文结合方式详述从设置入口到F7键启用“禁用驱动程序签…

作者头像 李华
网站建设 2026/10/22 18:24:34

基于JSP的网上书店系统设计与实现:从环境搭建到购物车事务全解析

简介&#xff1a;一份基于JSP的网上书店系统毕业设计论文文档&#xff0c;主要面向计算机相关专业学生&#xff0c;尤其适合正在准备课程设计或毕业设计的读者。该文档完整阐述了网上书店系统的设计与实现过程&#xff0c;内容涵盖JSP与JAVA技术基础、系统可行性分析与需求分析…

作者头像 李华
网站建设 2026/10/22 3:44:15

Wintel架构深度解析:Windows与Intel组合的硬件搭配与系统安装指南

1. 从“Wintel”这个词说起&#xff1a;一个时代的硬件与软件共生样本第一次听到“Wintel”这个说法&#xff0c;还是在一次老设备翻新的讨论里。当时有人提到“微特爾架构的老机器还能不能救”&#xff0c;我愣了一下才反应过来&#xff0c;这说的是Windows 操作系统加 Intel …

作者头像 李华
网站建设 2026/10/22 3:44:16

用航空规范治理AI废话病:Agent工程化的可靠实践

最近圈子里有个说法特别扎心&#xff1a;AI什么都会&#xff0c;就是不会少说。你让它总结三句话&#xff0c;它能给你铺垫三段背景&#xff1b;你让它查个报错&#xff0c;它先共情你加班辛苦&#xff0c;最后才把错误码夹在中间。Andrej Karpathy最近反复在提“software is c…

作者头像 李华