AI+系列:AI给的代码有坑,咋避坑系列-《AI 写的 C++ 内存池,差点让我的数据工厂原地爆炸》- 三-1-(5):
@[TOC]AI+系列:AI给的代码有坑,咋避坑系列-《AI 写的 C++ 内存池,差点让我的数据工厂原地爆炸》- 三-1-(5):
背景:机器人训练数据贵、慢、标注难???
试想一下,如果你是一个造机器人的工程师,想让机器人的 AI 视觉大模型学会精准抓取一个螺栓,你需要多少张照片来训练它? 答案是: 成千上万张不同角度、带有精准像素级标注的照片。【同时,真实世界中采集带标注的三维数据成本极高,我们称之为 Sim2Real(仿真到现实)的鸿沟。】
手工一张张拍?人工用鼠标去抠图?这得干到猴年马月! 为了解决这个痛点,用一台普通电脑,把 STEP 模型自动渲染成 100 张带像素级 Mask 的训练图(含 camera pose、COCO 格式)
解决方案:
- 只要丢给它一个工业 CAD 模型(比如 STL文件),它就能自动在虚拟空间中 360° 环绕拍照,瞬间吐出:
- 📸 RGB 真实渲染图:rgb/frame_XXXX.png
- 🏷️ 像素级语义分割 Mask (基于曲率算法,自动认出哪里是螺栓、孔洞、法兰):mask/mask_XXXX.png
- 📏 深度图(Depth) (告诉机器人距离多远),depth/depth_XXXX.png + .raw
- 📐 6DoF 相机位姿 (告诉机器人从哪个角度抓),camera_poses.json
- 📂 最后直接打包成 AI 训练最爱吃的 COCO/YOLO 格式。
- label_legend.txt【类别ID→名称→RGB颜色映射】、description.json【DeepSeek-V3 视觉API生成零件特征描述】
实际效果:
- 想看视频:
huhb_synthetic_data
- 不想看视频:也有图片:












【还有附带的:camera_poses.json、label_legend.txt、manifest.json,具体内容见附录】
巨人的肩膀:
- OpenGL 4.6 Specification
- Vulkan 1.3 Specification
- Khronos Group SPIR-V Whitepaper
- 历代GPU架构白皮书(NVIDIA Fermi至Blackwell,AMD GCN至RDNA 4)
系列文章规划:
- ((AI升级篇)OpenGL渲染与几何内核那点事-(二-1-(14):你的3D查看器,是怎么一步步先试着造个数据工厂,向学会“教”机器人看世界的而努力)
- (让 C++ 程序长出大脑:从“语音遥控器”到具身智能 Agent 的进化之路)------OpenGL渲染与几何内核那点事------(二-1-(15))
别再喂垃圾数据了!从3D查看器到AI数据工厂,一位工程师的“数据观”进化四重奏)------OpenGL渲染与几何内核那点事------(二-1-(16)) - OpenGL渲染与几何内核那点事-项目实践理论补充(一-2-(2)-当你的CAD需要处理“百万个螺栓”时:从内存爆炸到丝般顺滑)
- OpenGL渲染与几何内核那点事-项目实践理论补充(三-1-(3):番外篇-当你的CAD打开“怪兽级”STL时:从内存爆炸到零拷贝的极致优化
- OpenGL渲染与几何内核那点事-项目实践理论补充(三-1-(3):番外篇-当你的CAD打开“怪兽级”STL时:从内存爆炸到零拷贝的极致优化)
- OpenGL渲染与几何内核那点事-项目实践理论补充(一-3-(11):当你的CAD学会“吃显卡内存”:一场GPU内部的数据流转史诗)
- OpenGL渲染与几何内核那点事-项目实践理论补充(三-1-(1):当你的CAD需要同时打开10张2GB图纸时:从“new/delete”到内存池的进化之路))
- AI+【(实践出真知-Coze)你的智能体,从“人工智障”到“全能Agent”,—一个技术人的破局手记:教AI替我造了一座数据工厂- 二-1-(19):
- AI+系列【AI时代,咱们需要当数字世界的出题人】系列: 从打孔纸带到AI工厂主:我用4次进化,教AI替我造了一座数据工厂- 二-1-(18)
- 从 Ctrl+C 到 Coze 智能体的资料生成进化史)-你的 3D 查看器,是怎么一步步学会“写文档”的?-OpenGL渲染与几何内核那点事–(二-1-(17))
你的数据工厂终于能一键生成 RGB、语义 Mask、深度图和 COCO/YOLO 标注了,隔壁 AI 部门的研究生们开始把你的工具当成日常标配。然而好景不长,某天深夜,小李突然在钉钉上连发十张截图:“哥,数据生成到 800 张左右就越来越慢,跑到 1200 张直接崩了,是不是内存泄漏了?”
你心头一紧,打开 Visual Studio 的性能探查器一看:CPU 30% 的时间都在 malloc 和 free 里打转,堆内存碎片化得一塌糊涂。你突然意识到,这个看似简单的“数据工厂”,内部其实是一座永不停机的粒子对撞机——每一帧渲染都在疯狂地创建和销毁几何体、相机、光线、临时缓冲区……数量多达每秒数十万次。
“得自己搞个对象池了。”你心想。但你自己写?好像不太靠谱。于是你自然想到让 AI 帮忙生成一个高并发对象池。ChatGPT 倒是秒回了一段代码,长这样:
std::vector<std::shared_ptr<Bullet>> bullets;
bullets.push_back(std::make_shared<Bullet>());
看起来挺像那么回事。但你把它放进引擎里一跑,帧率反而从 20fps 掉到了 8fps。你愣住了:AI 写的代码,怎么比原来还慢?
你决定自己动手,从头把“对象池”这个高并发数据结构的地基彻底扒开,才发现:AI 写出能运行的代码毫无门槛,但底层的深水区,恰恰是 AI 的盲区。它没有硬件感知,不知道 CPU 缓存行的存在,不理解虚拟内存页的延迟提交,更没经历过高并发下缓存行对齐被反复擦除的痛苦。它只会根据统计概率,给出最符合“教科书”却在工业级项目里性能拉垮的代码。
下面,就让我们一起按照“需求演进、层层破产、不断打怪升级”的工业级迭代逻辑,把这个一切高并发数据结构的基石——内存管理(ObjectPool / BlockHeader / FreeNode)彻底扒光。
🛠️ 进化史:从一无所有到现代高性能无锁对象池
👴 Version 1.0:蛮荒时代 —— 随用随买的“纯情 new/delete”
实现逻辑:第一代程序员最单纯,就像你一开始用 OpenGL 加载个模型一样,每次需要粒子、子弹、光线,直接 T* obj = new T();用完 delete obj。
这在你的数据工厂渲染循环里,简直成了灾难现场:
- 慢:
malloc/new是内核级别的系统调用(或通用内存分配器在堆上做复杂查找),每次都要填表审批。一秒几十万次调用下来,CPU 全耗在审批流程上了。 - 内存碎片化:频繁申请释放大小不一的临时对象,让内存像被狗啃过一样满是空隙。最后空闲内存总量很大,可就是找不出一块连续的,连一个大一点的数组都分配不出来——程序直接崩溃。
❌ AI 坑点(V1.0 盲区)
如果你让 AI 写一个“高并发子弹管理系统”,90% 的 AI 会直接给你 std::vector<std::shared_ptr<Bullet>> 配合 new。AI 缺乏生命周期频率意识——它只觉得智能指针“安全”,却不知道 shared_ptr 内部还有一次额外的控制块内存分配和原子计数器开销。在高频场景下,这会直接让程序卡死在堆内存的全局锁竞争上,帧率掉成个位数。
想象一个工厂:你开了一个生产零件的工厂(程序运行)。每次需要零件,你就去仓库拿一个(new)。用完了扔掉(delete)。但问题来了:
每次去仓库拿零件都要填申请表、走审批(malloc/new 很慢)
零件到处乱扔,仓库碎片化了(内存碎片)
多个车间同时去拿零件,可能撞车(并发竞争)
ObjectPool 就是你的"零件自助货架":预先把一整排货架拉进来(VirtualAlloc大块内存),每个零件摆在固定位置,拿的时候不需要审批(无锁CAS),放回来直接扔进回收箱(FreeNode链表)。
🧔 Version 2.0:工业萌芽 —— 粗暴的“指针数组对象池”
改进逻辑:被现实暴打之后,你决定一次性向操作系统批发一大块连续内存,里面放一个指针数组,每个指针指向一个预先构造好的对象。这就像你不再每次去五金店买一颗螺丝,而是直接盘下一整个仓库,要用就去货架上拿。
AI 这时候也能帮你生成一段典型代码:
struct NaiveBlockHeader {
NaiveBlockHeader* next;
char* data; // AI 最爱的指针形式
};
乍一看没毛病,可当你把引擎里的粒子系统换成这套池子后,发现帧率居然只是从 8fps 提升到 12fps,离预期相去甚远。你用性能分析器追踪,发现CPU 缓存命中率惨不忍睹,Cache Miss 堆成了山。
问题根源:上面那个 char* data 只是一个指针,占了 8 字节,它指向的另一块内存才是真正的对象。CPU 每次访问对象时,必须先读 data 的地址,再“二次跳转”去访问实际货物。这种跳跃直接破坏了 CPU L1/L2 缓存的行预取机制——本来你可以一口气把相邻的几个对象全读进缓存,现在却不得不每跳一次就发生一次缓存未命中,性能直接暴跌 2-3 倍。
❌ AI 坑点(V2.0 盲区):柔性数组 vs 普通指针
AI 受限于海量的标准教科书语料,极度偏爱 char* data 或 void* ptr。但在 64 位系统下,一个指针本身就浪费 8 字节来存地址,更致命的是引入了解引用导致的 Cache Miss。AI 永远不会告诉你:“嘿,你其实可以用柔性数组让货物和货架头紧挨在一起。”
🧑💻 Version 3.0:底层榨干 —— “柔性数组 + Placement New” 的单线程极速池
你终于发现:货物应该紧贴在货架头后面,而不是隔着一条指针的“走廊”。你把 char* data 改成了 柔性数组(Flexible Array Member):
struct BlockHeader {
BlockHeader* next;
size_t used;
char data[]; // 柔性数组:不占空间,只是一个“内存标签”
};
它的秘密在于:char data[] 本身在 sizeof 里为 0 字节,它只是 C99 标准赋予的一个编译期占位符,代表紧跟在结构体尾部之后那一段内存的起始地址。你分配时一次性 malloc(sizeof(BlockHeader) + 对象大小 * 数量),货架头和货物就连绵不绝地贴在一起,CPU 可以顺序预取,缓存命中率瞬间飙升。
同时你引入了 Placement New:内存只管分配,对象的构造用 new (block->data + idx * sizeof(T)) T() 在原地强行唤醒。回收时,绝不调用 delete(那会把整个池子的内存都释放掉),而是手动显式调用 obj->~T(),把对象的灵魂抽走,肉身留下复用。
这一套组合拳下来,单线程场景下你的数据工厂简直健步如飞,粒子系统轻松跑到数百 fps。
但是,当你把渲染线程和 IO 线程同时开启,准备一边生成数据一边写磁盘时,程序直接当场崩溃——used 计数器和 next 指针被多个线程同时踩踏,发生了数据竞争(Race Condition)。你试着加了一把 std::mutex 大锁,结果多线程全在门口排队,性能一夜回到解放前,甚至不如 V1.0 的单线程版本。
❌ AI 坑点(V3.0 盲区):Placement New 的生命周期黑洞
AI 最常见的错误是:回收时直接用 delete obj,连带把对象池自己的核心内存也给释放掉了——整个池子直接报废。正确的做法是显式调用 obj->~T()。可惜 AI 在写泛型模板时,极高概率直接漏掉析构调用,导致对象内部管理的 std::string 堆内存、文件句柄等资源发生隐式泄漏,几天不关机内存就吃光。
🚀 Version 4.0(现代工业终极版):无锁 CAS + 缓存行对齐 + 虚拟内存大页批发
彻底解决多线程竞争与硬件开销,你集成了从现代智能驾驶平台、高频网络库中锤炼出来的硬件级特性,打造了终极版本。我们直接来看这份凝结了前三代血泪教训的工业级骨架代码:
#include <atomic>
#include <new>
#include <cstddef>
// 1. FreeNode:回收箱里的回收条,复用对象自身内存,零空间开销
struct FreeNode {
std::atomic<FreeNode*> next;
};
// 2. BlockHeader:货架头,必须独占缓存行,防止伪共享
struct alignas(64) BlockHeader {
std::atomic<BlockHeader*> next;
std::atomic<size_t> used;
char data[]; // 柔性数组,紧跟在 Header 后面的货物内存
};
// 3. ObjectPool 核心骨架(展示工业级无锁回收与分配)
template<typename T, size_t BlockSize = 4096>
class UltraObjectPool {
private:
std::atomic<BlockHeader*> m_blocksHead{nullptr};
std::atomic<FreeNode*> m_freeListHead{nullptr};
public:
T* Allocate() {
// 优先从回收箱(FreeNode)无锁 Pop
FreeNode* oldFree = m_freeListHead.load(std::memory_order_relaxed);
while (oldFree && !m_freeListHead.compare_exchange_weak(
oldFree, oldFree->next.load(std::memory_order_relaxed),
std::memory_order_release, std::memory_order_relaxed)) {
// 自旋重试
}
if (oldFree) {
return ::new (static_cast<void*>(oldFree)) T();
}
// 如果回收箱空了,从当前 Block 线性切分
// 实际工业界会配合 VirtualAlloc / mmap 一次性批发大页内存
// size_t idx = block->used.fetch_add(1, std::memory_order_relaxed);
// return ::new (block->data + idx * sizeof(T)) T();
}
void Deallocate(T* obj) {
if (!obj) return;
obj->~T(); // 显式析构,释放内部资源
FreeNode* node = reinterpret_cast<FreeNode*>(obj);
node->next.store(m_freeListHead.load(std::memory_order_relaxed), std::memory_order_relaxed);
while (!m_freeListHead.compare_exchange_weak(
node->next, node,
std::memory_order_release, std::memory_order_relaxed)) {
// 自旋重试
}
}
};
有了它,你的数据工厂彻底脱胎换骨。渲染线程和 IO 线程无锁并发,CPU 缓存行不再乒乓,大块内存直接从操作系统批发。生成一万张训练数据所需的时间,从最初的一小时压缩到了三分钟。
BlockHeader:一排货架,带一个计数器显示"已用几个".代码本质就是alignas(64) 的内存块头 + atomic<size_t> 已用计数 + 柔性数组 char data[]。char data[] 就像货架头上的标签——标签本身不占空间,它只是告诉你"货架头后面就是货物区"。C语言标准规定,大小为0的数组放在结构体最后,不占 sizeof,但可以合法访问后面的内存。
FreeNode:回收箱里的一张纸条,写着"这个位置空了".代码本质就是atomic<FreeNode*> 链表,无锁回收
ObjectPool:管理所有货架和回收箱的厂长.代码本质就是分块链表 + 原子操作的无锁分配
🎯 深度复盘:为什么这些是 AI 容易写错的区域?(硬核技术论证)
AI 时代的最高效学习,不是看它写了什么,而是看它悄悄漏掉了什么。以下四个技术点,是区分“AI 玩具代码”与“工业专家代码”的试金石。
1. 缓存行对齐(
alignas(64))与伪共享(False Sharing)AI 为什么漏:AI 本质是文本概率模型,在庞大的开源代码中,95% 的人写结构体是不加
alignas(64)的。AI 无法主观感知 CPU 的高速缓存是以 64 字节(Cache Line) 为基本单位进行同步的。致命后果(Cache Ping-Pong):
现代多核 CPU 每个核心都有自己的 L1/L2 缓存。当一个核心修改了某个缓存行中的任意一个字节,整个缓存行都会被标记为“脏”,其他核心如果想访问这行里的其他变量,哪怕变量之间毫无逻辑关系,也必须从主存重新加载整行。如果多个线程高频修改同一个BlockHeader里的不同原子变量(比如next和used),而它们不幸挤在同一个 64 字节的缓存行里,CPU 就会在不同核心之间疯狂来回同步这行缓存。在硬件层面,这会挂起整个 CPU 流水线,高并发时的性能甚至比加重锁还要慢数倍。解决方案就是
alignas(64),强制将BlockHeader对齐到 64 字节边界,确保它独占一个或多个完整的缓存行,相邻的BlockHeader不会互相拖累。这也是为什么你在 Linux 内核、DPDK、TBB 等工业级代码里随处可见__attribute__((aligned(64)))的原因。
2. 虚拟内存大页批发(
VirtualAlloc/mmap)与延迟提交AI 为什么漏:AI 极度依赖标准库的跨平台通用性,所以它几乎只会推荐
malloc或std::allocator。工业界真相:
通用的malloc内部为了兼顾 8 字节到数 GB 的各种分配,维护了一套极其复杂的内存分级和全局锁。既然你已经决定手写专属的对象池,就必须彻底绕过malloc的干扰,直接找操作系统批发土地。
- Windows:
VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)- Linux:
mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)这样做有三个巨大的好处:
- 页对齐:拿到的内存保证是 4KB 页对齐的,TLB(快表)利用率最高。
- 延迟提交(Lazy Commit):你一口气 reserve 1GB 虚拟地址空间,但操作系统在你真正读写这些页之前,根本不会分配物理内存。这意味着池子可以提前声明海量“虚拟容量”,而物理内存消耗却按需增长。
- 大页(Huge Pages):更进一步,可以用
MAP_HUGETLB(Linux)或Large-PageAPI(Windows)申请 2MB 甚至 1GB 的大页,大幅降低 TLB Miss,这是高频交易系统级别的优化,AI 的通用语料库几乎不会涉及。
3. 无锁 CAS 操作的精髓:
compare_exchange_weak与内存顺序AI 生成的无锁代码最常见的错误之一,就是滥用
compare_exchange_strong和默认的std::memory_order_seq_cst(全局顺序一致性)。
weakvsstrong:compare_exchange_strong在 x86 平台上通常是lock cmpxchg的一条指令,基本不会“伪失败”。但在 ARM、RISC-V 等弱内存序架构上,LL/SC(Load-Link/Store-Conditional)指令容易因中断等原因伪失败。compare_exchange_weak允许这种伪失败,并用循环包裹重试,在循环中weak版本的性能远超strong。工业界跨平台的正确做法是:永远在循环中使用weak。内存顺序(Memory Order):
如果盲目使用默认的seq_cst,会强制 CPU 每次都进行全局的同步和流水线冲刷,无锁的优势被彻底葬送。
在你的对象池中:
- 分配时
m_freeListHead.compare_exchange_weak(..., memory_order_release, memory_order_relaxed),保证释放的写入对后续获取可见。- 加载
oldFree->next使用memory_order_relaxed,因为此时我们已经独占了这个节点的访问权,无需同步。
这种细粒度的内存顺序控制,是 C++ 内存模型给专家准备的手术刀,也正是 AI 最难自动推导的部分。
4. 柔性数组(Flexible Array Member)——零字节的奇迹
struct BlockHeader { ... char data[]; };中data的sizeof为 0。它仅仅是一个编译期标签,代表结构体尾部的地址偏移。你分配sizeof(BlockHeader) + N * sizeof(T)字节,data就自然而然地指向了那 N 个T的连续存储区。这相比于存一个 8 字节的指针,不仅节省了空间,更关键的是消灭了二次解引用,让 CPU 的硬件预取器能够连续地“一网打尽”多个对象,大幅提升缓存命中率。这是系统编程中“数据导向设计”(Data-Oriented Design)的经典体现——代码与数据在物理内存中的排列,直接决定了你的性能上限。
🔍 终极自检:测测你是否已经“透过现象看本质”
学完这一课,用下面这 4 个最锋利的问题去拷打 AI(或者自测)。如果 AI 顾左右而言他,说明它给出的内存管理代码绝对有坑:
| # | 硬核自检问题 | 💡 破局的关键底层本质 |
|---|---|---|
| 1 | sizeof(BlockHeader) 是多少?里面的 char data[] 到底占几个字节? |
零字节。它只是一个编译期占位符,代表结构体尾部的内存偏移量,绝对不能用 char* 代替。 |
| 2 | 为什么 FreeNode 链表不需要额外的内存存储空间? |
内存复用。对象被回收后它就是一具死尸,我们直接强行把这块死尸的内存重写(reinterpret_cast)成 FreeNode 指针,一分钱空间都不多占。 |
| 3 | 无锁 CAS 操作中,compare_exchange_weak 为什么带个 weak?为什么用 relaxed 内存顺序? |
硬件层面允许伪失败,weak 在循环中性能远超 strong;内存顺序如果盲目用默认的 seq_cst(全局顺序一致性),会强制 CPU 刷新流水线,无锁的优势会被彻底葬送。 |
| 4 | 如果池子满了,VirtualAlloc 拿到的新大块内存没有调用任何构造函数,为什么可以直接拿来切分使用? |
虚拟内存只是一张地址映射表,通过 Placement New,我们只在真正需要的时候才在指定的物理地址上“唤醒”对象生命周期。 |
把这份工业级对象池集成到你的数据工厂之后,那个曾经让你头疼的“跑 1200 张就崩”的问题彻底消失了。小李在群里发了个拱手表情:“哥,你连内存都亲自管了,这数据工厂真成铁打的流水线了。”
你看着屏幕上稳定在 60fps 的实时渲染窗口,心想:AI 写代码确实快,但真正要让软件像一台精密的机器那样毫秒不差地运转,靠的还得是工程师对每一寸内存、每一个时钟周期的深刻理解。
从“一个简单的 STL 查看器”到“具身智能合成数据生成器”,再到“连内存都不放过”的工业底座,这趟旅程的每一站,都是一次从“能用”到“极致”的自我超越。
代码仓库入口:
- github源码地址(https://github.com/AIminminAI/Huhb3D-Viewer)。
- gitee源码地址(https://gitee.com/aiminminai/Huhb3D-Viewer)。
本文涉及:
- https://github.com/AIminminAI/Huhb3D-Viewer/blob/main/src/core/tool_registry.cpp
- https://github.com/AIminminAI/Huhb3D-Viewer/blob/main/src/agent/AIAgentController.cpp
- 如果想像唠嗑一样,去了解一些小知识,快去看看视频吧:
- 认准一个头像,保你不迷路:
- 抖音:搜索“GodWarrior”
- 快手:搜索“AIYWminmin”
- B站:搜索“宇宙第一AIYWM”
您要是也想站在文章开头的巨人的肩膀啦,可以动动您发财的小指头,然后把您的想要展现的名称和公开信息发我,这些信息会跟随每篇文章,屹立在文章的顶部哦
附录:
camera_poses.json
[
{
“frame_id”: 0,
“position”: [0.0, 0.0, 5.0],
“rotation_euler”: [0.0, 0.0, 0.0],
“fov_degrees”: 45.0,
“view_matrix”: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, -5.0],
[0.0, 0.0, 0.0, 1.0]
],
“projection_matrix”: [
[2.414, 0.0, 0.0, 0.0],
[0.0, 2.414, 0.0, 0.0],
[0.0, 0.0, -1.002, -0.200],
[0.0, 0.0, -1.0, 0.0]
]
},
{
“frame_id”: 1,
“position”: [1.18, 0.0, 4.86],
“rotation_euler”: [0.0, -13.6, 0.0],
“fov_degrees”: 45.0,
“view_matrix”: [
[0.972, 0.0, 0.236, -0.0],
[0.0, 1.0, 0.0, 0.0],
[-0.236, 0.0, 0.972, -5.0],
[0.0, 0.0, 0.0, 1.0]
],
“projection_matrix”: [
[2.414, 0.0, 0.0, 0.0],
[0.0, 2.414, 0.0, 0.0],
[0.0, 0.0, -1.002, -0.200],
[0.0, 0.0, -1.0, 0.0]
]
}
]
label_legend.txt
Semantic Label Color Legend
Category -> (R, G, B) in 0-255 range
0 FreeSurface 127 127 127
1 HorizontalPlane 0 0 255
2 LateralPlane_X 0 255 0
3 LateralPlane_Z 255 0 0
4 NearHorizontal 255 255 0
5 NearLateral_X 255 0 255
6 NearLateral_Z 0 255 255
7 Degenerate 255 127 0
8 Reserved1 127 0 255
9 Reserved2 0 127 255
manifest.json
{
“version”: “2.0”,
“generator”: “Huhb3D-SyntheticDataPipeline”,
“rgb_count”: 100,
“mask_count”: 100,
“depth_count”: 0,
“has_legend”: true,
“has_ai_description”: false,
“has_camera_poses”: false
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)