Tearing into ChatGPT's Container Environment A Hacker News user discovered that ChatGPT's container environment provides 56 CPU cores and 4GB of RAM, prompting an investigation into the infrastructure. The containers run on gVisor, a sandboxed kernel, and the high core count is due to oversubscription shared among containers. This reveals the scale of compute resources OpenAI allocates for AI workloads. I'd say the Cloud is now the data center, where, boom, you have these at scale. You have such massive resources available. They're readily available, easy to use. I've always viewed that computing follows the Gas Law. It fills the available space. It always keeps expanding and it's really more a statement where ..... how much computing do you want to do? Well, as much as I can afford. How many more simulations do you do, before you send a chip to the fab? Well, as many as I can get done before I think I've exhausted it, but there are always more tests you can run. How many more analysis of your radiology results do you want, running through the AI algorithms to determine if it's carcinomic or not? Well, as many as you can afford, right? Please give me the best results you can, and the list goes on and on and on. So to me, computing has always wanted to fill the available space where the available space is often more limited by economics than anything else. And if I make the unit cost of computing lower, and the ability to reach the data ..... Every time there is a dramatic decrease in that, you open up new opportunities for computing. If we use the AI example, hidden Markov models, convoluted neural nets, et cetera, those ideas were around, all of a sudden that got economical, and Cloud made it economical and all of the sudden Cloud made datasets large enough that I could use learning algorithms that before were infeasible, now became feasible as well. So that combination of compute capacity and datasets, allowed AI to start demonstrating meaningful breakthroughs and now it's sort of like, “Wow, how much computing do you need for AI?” Well, the learning algorithms, it's almost unlimited, right? Really, if you give me another thousand GPUs in my GPU farm, I'll use them all. Many of the hardest problems in computing have always demonstrated this characteristic, whether it's weather prediction, whether it's predictive modeling, whether it's computational fluid dynamics, these are n-complexity algorithms that, boy, you can just keep throwing computing at them. - Pat Gelsinger in his 2019 Oral History with CHM . A few months back, while randomly browsing old Hacker News submissions, I came across a rather interesting submission from Simon Willison on ChatGPT Containers https://simonwillison.net/2026/Jan/26/chatgpt-containers/ . What immediately caught my attention was not the post itself but one of the comment threads on HN https://news.ycombinator.com/item?id=46772003 : xnx: How much compute do you get in these containers? Could I have it run whisper on an mp3 it downloads? simonw: That might work You would have to figure out how to get Whisper working in there but I'm sure that's possible with a bit of creativity concerning uploading files and maybe running a build with the available C compiler. It appears to have 4GB of RAM and 56 ? CPU cores https://chatgpt.com/share/6977e1f8-0f94-8006-9973-e9fab6d24418 56 LPs?? 4GB of RAM?? One of the users in the thread, named tintor, who appears to be ex-OpenAI O1 reasoning model and code interpreter , briefly mentioned that the cores are shared with other containers https://news.ycombinator.com/item?id=46772442 . However, even with oversubscription, the number seemed baffling. So, I decided to further investigate this. In this blog post, we will dive into what their container environment looks like. Before we begin, note that all of my chat prompts and their subsequent containerized executions were performed on ChatGPT's paid plan - ChatGPT Plus. They were running GPT 5.5 Extended Thinking. Let us start by focusing on ChatGPT's container environment. If we ask it to run dmesg https://man7.org/linux/man-pages/man1/dmesg.1.html and report the output of the command, here is what we get: 0.000000 Starting gVisor... 0.541400 Checking naughty and nice process list... 0.648754 Mounting deweydecimalfs... 0.661315 Daemonizing children... 1.145987 Searching for needles in stacks... 1.455202 Verifying that no non-zero bytes made their way into /dev/zero... 1.791613 Creating cloned children... 2.209217 Rewriting operating system in Javascript... 2.593286 Checking naughty and nice process list... 2.602568 Letting the watchdogs out... 2.972217 Creating bureaucratic processes... 3.195269 Setting up VFS... 3.254751 Setting up FUSE... 3.723550 Ready This is strong evidence that ChatGPT uses gVisor as a sandboxing environment for code execution. To better understand gVisor, I encourage checking out Emma Haruka Iwao's 2019 talk https://youtu.be/Ur0hbW K66s?si=QDaudvBSQPsbOpVf and Ye Lin's recent blog post https://blog.yelinaung.com/posts/gvisor/ . Andrea and Remzi Arpaci-Dusseau of OSTEP fame have also co-authored a paper on it https://www.usenix.org/system/files/hotcloud19-paper-young.pdf . Here is what the gVisor team has to say about their product https://gvisor.dev/blog/2024/09/23/safe-ride-into-the-dangerzone/ : gVisor is a container security solution. ….. An open source project written in Go, gVisor was released in May 2018 by Google under the Apache 2.0 license. It runs on Linux and integrates with all popular container management software, such as Docker, Podman, or Kubernetes. At its core, gVisor is an application kernel that implements a substantial portion of the Linux system call interface. This means gVisor sits between a container and the Linux kernel and plays both roles: from the container's perspective, gVisor acts as a kernel, but from Linux's perspective, gVisor is just a regular application. That means the container can no longer directly interface with the Linux kernel. This is a massive reduction in attack surface. gVisor manages to pull the above trick with the help of two components: - Sentry is the component that runs the containerized application. It intercepts every system call that the application makes and reimplements it in Go. As part of this, it may decide to do one or more system calls to the host Linux kernel. However, it's heavily restricted with a strict seccomp filter that's why system calls like open, socket, or exec are not allowed . - Gofer is a component that runs outside the container and is responsible for filesystem operations. The sentry may make I/O requests to the gofer. The gofer will independently validate them, then perform these I/O operations on the container's behalf that's how the container can read files from the host filesystem, even though open is not allowed from the sentry . Circling back to the above dmesg output, you might notice that the logs appear somewhat nonsensical. Why is that? By default, dmesg is used to print the messages in the kernel ring buffer. The modern interface to read this message buffer involves /dev/kmsg . The older interface involved reading from syslog. However, if you ask ChatGPT to perform the following command in its container: ls -l /dev/kmsg , you will get an "ls: cannot access '/dev/kmsg': No such file or directory" error. This is because /dev/kmsg is not present in their gVisor sandbox, and the command is likely to fall back to the older syslog method. So what happens next? Observe the 103 syscall number in gVisor's linux64.go https://github.com/google/gvisor/blob/master/pkg/sentry/syscalls/linux/linux64.go L148 . 103: syscalls.PartiallySupported "syslog", Syslog, "Outputs a dummy message for security reasons.", nil , This is Sentry's syscall table that maps gVisor's syscall API to Linux's corresponding syscall numbers. The 103 syscall on Linux is syslog https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md , which is getting wired to gVisor's Syslog handler. The code for the handler is at sys syslog.go https://github.com/google/gvisor/blob/master/pkg/sentry/syscalls/linux/sys syslog.go L34 , which in turn invokes syslog.go's Log function https://github.com/google/gvisor/blob/master/pkg/sentry/kernel/syslog.go L51 , which returns a dummy copy of the syslog. Next, let us focus on its execution environment and the 56 LP claim. If I ask it to run nproc and report the output of the command, I get 56. Furthermore, a cat /proc/cpuinfo returns the following: processor : 0 vendor id : GenuineIntel cpu family : 6 model : 79 model name : unknown stepping : unknown cpu MHz : 2294.687 cache size : 8192 KB physical id : 0 siblings : 56 core id : 0 cpu cores : 56 apicid : 0 initial apicid : 0 fpu : yes fpu exception : yes cpuid level : 13 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm pni pclmulqdq vmx ssse3 fma cx16 pcid sse4 1 sse4 2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf lm abm 3dnowprefetch fsgsbase bmi1 hle avx2 smep bmi2 erms invpcid rtm rdseed adx smap xsaveopt md clear bogomips : 2294.69 clflush size : 64 cache alignment : 64 address sizes : 46 bits physical, 48 bits virtual power management: I am only showing the first processor's info. There are 55 processor infos that come after this. However, they all differ only in the processor , core id , apicid , and initial apicid fields. For the next processor output, these 4 fields would have the value of 1, and the subsequent processor would have it as 2, and so forth. The gVisor code corresponding to this seems to originate from pkg/cpuid/cpuid amd64.go https://github.com/google/gvisor/blob/master/pkg/cpuid/cpuid amd64.go L312 , particularly the WriteCPUInfoTo call: // WriteCPUInfoTo is to generate a section of one cpu in /proc/cpuinfo. This is // a minimal /proc/cpuinfo, it is missing some fields like "microcode" that are // not always printed in Linux. Several fields are simply made up. From the code, the model name and stepping are hardcoded to "unknown". Furthermore, gVisor pretends that all CPUs are in the same socket and the physical id is always set to 0. The presence of fpu , fpu exception , and wp write protect is also hardcoded to return "yes". Address sizes are not read from hardware either; they are predetermined to be 46 bits physical, 48 bits virtual. Perhaps the most interesting one is cache size , which is completely made up at 8192 KB with the comment: // Pretend the CPU has 8192 KB of cache. Note that real /proc/cpuinfo exposes total L3 cache // size on Intel and per-core L2 cache size on AMD as of Linux 6.1.0 , so the value of this // field is not really important in practice. Any value that is chosen here will be wrong // by an order of magnitude on a significant chunk of x86 machines. // 8192 KB is selected because it is a reasonable size that will be effectively usable on // lightly loaded machines - most machines have 1-4MB of L3 cache per core. But some fields contain real CPUID data. The vendor id , cpu family , model , and flags are all parsed using CPUID . CPUID https://en.wikipedia.org/wiki/CPUID is an instruction that is used to query processor details. Intel concisely describes this in their Architecture Software Developer Manuals https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html : This instruction returns the family, model, and for some processors a brand string for the processor that executes the instruction. It also indicates the features that are present in the processor and gives information about the processor's caches and TLB. ..... To obtain processor identification information, a source operand value is placed in the EAX register to select the type of information to be returned. When the CPUID instruction is executed, selected information is returned in the EAX, EBX, ECX, and EDX registers. To fetch information from CPUID , WriteCPUInfoTo invokes the following call https://github.com/google/gvisor/blob/cfb7c0629521099eb14d7bd86e9fbfa47287a640/pkg/cpuid/cpuid amd64.go L315 : ax, , , := fs.query featureInfo Inside native amd64.go , the featureInfo constant https://github.com/google/gvisor/blob/cfb7c0629521099eb14d7bd86e9fbfa47287a640/pkg/cpuid/native amd64.go L46 is defined as: featureInfo cpuidFunction = 0x1 // Returns basic feature bits and processor signature. fs.query is a thin wrapper that forwards the call to Native.Query https://github.com/google/gvisor/blob/cfb7c0629521099eb14d7bd86e9fbfa47287a640/pkg/cpuid/native amd64.go L147 : // Query executes CPUID natively. // // This implements Function. // //go:nosplit func Native Query in In Out { if int in.Eax < len allowedBasicFunctions && allowedBasicFunctions in.Eax { return native in } else if in.Eax = uint32 extendedStart { if l := int in.Eax - uint32 extendedStart ; l < len allowedExtendedFunctions && allowedExtendedFunctions l { return native in } } return Out{} // All zeros. } In the above code, Native.Query checks whether the requested CPUID is in an allowlist. If allowed, it calls native , whose implementation lives in the assembly file native amd64.s https://github.com/google/gvisor/blob/cfb7c0629521099eb14d7bd86e9fbfa47287a640/pkg/cpuid/native amd64.s L17 . In this scenario, the source operand value 0x1 is put into the EAX register, and when the CPUID instruction is executed, the selected information is returned in the EAX , EBX , ECX , and EDX registers. TEXT ·native SB ,NOSPLIT|NOFRAME,$0-24 MOVL arg Eax+0 FP , AX MOVL arg Ecx+4 FP , CX CPUID MOVL AX, ret Eax+8 FP MOVL BX, ret Ebx+12 FP MOVL CX, ret Ecx+16 FP MOVL DX, ret Edx+20 FP RET The important thing to note here is that gVisor is not fabricating this data, unlike the others. It is running the real CPUID instruction. As for its runtime environment, a hypothesis can be made that OpenAI's gVisor is running inside a containerized environment managed by a hypervisor on a cloud infrastructure. Notice how the hypervisor flag is set in /proc/cpuinfo . Microsoft describes this approach to hypervisor discovery https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/feature-discovery in its docs: Before using any hypervisor interfaces, software should first determine whether it's running within a virtualized environment. On x64 platforms that conform to this specification, this is done by executing the CPUID instruction with an input EAX value of 1. Upon execution, code should check bit 31 of register ECX the “hypervisor present bit” . If this bit is set, a hypervisor is present. In a non-virtualized environment, the bit will be clear. In such a scenario, the hypervisor would intercept CPUID and modify its results. Hypervisors "can" present a fake CPU model - Chips and Cheese wrote a blog post describing this https://chipsandcheese.com/p/why-you-cant-trust-cpuid back in 2022. Therefore, it is hard to say whether the underlying hardware of the host might be Intel-based or not. The CPU family 6 and model 79 themselves seem to correspond to the Broadwell CPU family https://openbenchmarking.org/s/Intel+Xeon+E5-2683+v4 . It is rather old, being the fifth generation https://en.wikipedia.org/wiki/Broadwell microarchitecture of the Intel Core processor from 2014. A handy way to find out the architecture correlated with a CPU family is by peeking at the constants in Linux's source code at linux/arch/x86/include/asm/intel-family.h https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/intel-family.h - " define INTEL BROADWELL X IFM 6, 0x4F " There is another twist to this tale When I asked it to run /proc/cpuinfo - only a few hours apart - I managed to get a completely different set of model numbers and flags : ..... vendor id : GenuineIntel cpu family : 6 model : 106 ..... siblings : 56 cpu cores : 56 ..... flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm pni pclmulqdq vmx ssse3 fma cx16 pcid sse4 1 sse4 2 x2apic movbe popcnt tsc deadline timer aes xsave avx f16c rdrand hypervisor lahf lm abm 3dnowprefetch fsgsbase tsc adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm avx512f avx512dq rdseed adx smap clwb avx512cd sha ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves avx512vbmi umip avx512 vbmi2 gfni vaes vpclmulqdq avx512 vnni avx512 bitalg avx512 vpopcntdq la57 rdpid fsrm arch capabilities ..... This CPU model is relatively newer, and is based on the Ice Lake https://en.wikipedia.org/wiki/Ice Lake microprocessor Sunny Cove family from 2019. This indicates that there are multiple SKUs in the environment being mapped on a need-to basis. There is something fascinating we can do here. CPUID is an unprivileged instruction, not a syscall, and it executes in ring 3 without trapping the kernel. Therefore, we can create and execute a small C program that invokes the CPUID instruction and parses various processor details out of it. Here is the program that I created with the help of Claude - the underlying comments and explanations are my own. include