Serving 2 Million Models Without Melting: Scaling the Hugging Face Hub — Arek Borucki, Hugging Face

summarized

TLDR

Hugging Face scaled its infrastructure to serve 3 million models and 14 million users by decoupling metadata storage in MongoDB from binary artifacts in object storage, implementing denormalized pre-tokenized search with Atlas Search (Lucene), and using two-tier Kubernetes autoscaling (HPA + Cast AI) with a planned migration to KEDA for event-driven scaling. The talk emphasizes optimizing for read-heavy workloads, pushing non-primary operations to secondary replicas, and the upcoming adoption of database sharding to handle continued growth.

Key points

  • Hugging Face hosts 3 million public models and 1 million datasets, growing from 20,000 models a few years ago.
  • Metadata (model info, user data, configs) is stored in MongoDB Atlas; actual model artifacts are in cloud object storage like AWS S3.
  • Search is optimized by pre-tokenizing model names at insert time and using MongoDB Atlas Search (Apache Lucene) for autocomplete, avoiding slow regex operations.
  • The MongoDB cluster uses a single primary for writes and strong consistency, while reads and complex aggregations are distributed to secondaries; a hidden node handles reporting and heavy queries.
  • Kubernetes horizontal pod autoscaler (HPA) scales pods from 10 to 500 based on CPU/memory, but the team plans to migrate to KEDA for scaling on application metrics like request rate.
  • Node-level autoscaling via Cast AI ensures that when pods are pending due to capacity, new nodes are added automatically.
  • Future scaling will involve MongoDB sharding to horizontally distribute data across multiple replica sets.
  • The infrastructure is designed to be invisible to users—complexity is hidden behind a simple interface.

Tools mentioned

Techniques

  • horizontal pod autoscaling (HPA)
  • database sharding
  • denormalization for read optimization
  • pre-tokenization at insert time
  • read replicas distribution
  • separating metadata from binary storage
  • event-driven autoscaling (KEDA)
Transcript (captions)
[music] Good afternoon everyone. I have a question. How many of you knows hugging face? Nice. How many of you already use hugging face? Amazing. Almost everyone. But I think we still have opportunity to grow our usage. My name is Ar Borutki. I work as machine learning platform and database engineer at hugging face. Today I would like to walk you through how hugging faced scaled infrastructure and how we ended up serving 3 million models to developers around the world. I would like to share architectural decisions we made, challenges we faced, and lessons we learned while scaling one of the fastest growing open-source AI communities in the world. I hope you will enjoy it and let's get started. Before I dive into technical details, let's talk about scale. Today, Hugging Face serves more than 14 million users and this number is growing very fast, especially in the last couple of months. We host three million public models, 1 million data sets, 50,000 organizations, and not only hobbyists or scientists. More than 30% of Fortune 500 use hugging face as a part of AI workflows. Just to give you some perspective, few years ago we had 20,000 models. Today 3 million. It is around 150x increase in just last couple of years. And this grow is exactly why I'm here today talking about infrastructural decisions that keep the hub healthy at scale. This is how fast the number of public models is growing on the hub. Every big release like llama or deepseek generated thousands of new models on top and our infrastructure needs to handle that and it is not only models also data sets. In 2022, we had 10K. In 2024, 100K. Less than a year ago, we had 500K. Today, 1 million. All this data must be stored, indexed, and also must be searchable. And that's the hardest part. And this is also the reason why we had to rethink our search. At 20,000 models, any query is fast. Even without an index, trust me, no one would notice. At 3 million, same approach breaks. Imagine what would you do if the hub search would be slow. you would just leave and go somewhere else. And this is also what user are doing. They expect fast instant results. With 14 million users, even 1% is a not small number. It is 140,000 of people hitting slow search at scale. P99 is much more important than P50 and we are paying a lots of attention to P99 and that's the reason why we invest in premputee tokens denormalize optimize for read collection in MongoDB full text search based on Apache lucine Kubernetes autoscaling and soon in database sharding. The next slides will show you how high level architecture when user interact with the hugging face hub his request flows from the front end to the hub API. The hub is running on Kubernetes. Currently we are using horizontal pot autoscaler. During spikes new ports scale up automatically to handle the load and scale back down when traffic drops. This help us to keep the hub healthy without manual intervention. Next the request goes to MongoDB Atlas which is source of true for our metadata. And there is one point that sometimes surprise people. MongoDB does not store the models themselves. It stores everything about the models. What doesn't mean in practice in MongoDB we hold all the metadata users repositories models data sets buckets spaces information configuration data billing data access control and more. The actual models artifacts, tokenizer files, cart assets and configuration files are stored separately in cloud object storage such as AWSS3. This separation of concern let us scale metadata independently from binary storage and compute independently from both. We can optimize each component individually for specific workload. Now let's check how search works in details. For example, someone wants to search the model on the hub and let's say that's llama. So someone type llama into hugging face search bar. His request flows through the hub to an optimized read collection on MongoDB. And this is not our main repo collection when we keep all the data. It's a separate denormalized copy only for reads and listings. The key information is on the left. We tokenize model names on insert time, not at query time. For example, someone wants to publish model meta minus llama/ llama 3.18b. We split the long model name into small tokens like meta lama 3.18b and we store them in an array in MongoDB document. Next, Atlas search which is using Apache Lucin under the hood use autocomplete to find matching models instantly. This is example of single document from our model collection. In this example, I'm using find one method. I want to find model ID meta minus llama/lama 3.1. So that's the model from the previous slide. And I'm projecting only search token array. And we see that all those premputee tokens are part of this array. So we have metal lama 3.1 metal lama etc. Next there must be a query. In the past we were using classical MongoDB find method on models collection with reax operator and this reax operator we're searching in search tokens arrays models which are equal to llama and then we were s sorting results by trending score which is calculated every five minutes. This is number of downloads and number of likes is as far as I remember from the last seven days. This solution was working well as long as data set was small. Reax doesn't scale well. So when our data set started to grow very quickly, we started to having problems with latency. So we decided to switch to Atlas search that's a feature which is using Apache lucine under the hood. So MongoDB doesn't provide in core MongoDB server full text search there is additional process MongoD uh this MongoDBT process is a wrapper around Apache lucine for end user users this is transparent you are just using Unifi MongoDB query API and when You use aggregation pip pipeline together with dollar search operator. MongoDB will know that you would like to search Apache Lucin index. Obviously you need to put the name of this index which is in this scenario model search autocomplete model equal to llama path search tokens and we still sort results by trending score and this solution is much more efficient and is so far scale Well, so we don't have any more latency issues in our search bar. First two results returned by previous query. First metal lama has trending score 33. Second one 14. But hugging face hub is not only search. We have hundreds of different services in hugging face which are utilizing which are using MongoDB to handle million of queries. We use seven nodes MongoDB clusters cluster with multiple machines. We can distribute queries across multiple nodes. So no single node become read bottleneck. This is how it works. Application talk to the MongoDB cluster. All inserts, deletions or updates goes to single primary because only primary can handle them. However, we are distributing reads across multiple machines. We also have one analytic hidden node. What does it mean? This mean this node is invisible from application. MongoDB driver is not routing any queries to this hidden node. This node is still replicating data from primary but is not interacting with interacting with production traffic. We are connecting directly to this node and we use him for any kind of reporting traffic on or any kind of really heavy queries. All secondaries continuously tail the oplog from primary keeping cluster in sync. Now let's have a look what actually is running on secondaries. First all queries which doesn't require the latest data goes to secondaries. Only queries that must have strong consistency stay on primary and we are paying a lots of attention to this. We are paying lots of attention to the queries which must run on primary. Second, complex aggregations. Aggregations pipelines that scan large amount of data, sort, group or transform the data should not go on primary. They are heavy. Secondaries are better placed for them. Third, change streams. We react to data changes in real time for several reasons. For example, caching validation, sync to different data store technologies like for example AWS, Red Shift or for eventdriven workloads. Those kind of operations are also not very light and they should stay impossible on secondaries. Fourth, all ad hoc queries, reporting queries, maybe some experimental queries go to hidden MongoDB replicas set member which is isolated from production traffic. The pattern is simple. Primary should focus on what only primary can do. Anything else can be pushed to different machines. However, with 14 million users, 3 million models and our grow soon single MongoDB replica set will not be enough. The next step is sharding. Sharding means scaling your database horizontally. Instead of putting full data set on one replic on one replica set cluster, we are going to cut data into pieces and put each piece on separate chart. Each shard will have his own replication primary and secondary. So we will keep replication just multiplied. The key difference between replica set cluster and sharded cluster is replica set keep full data set on each node. Sharded cluster keep only part of the data on each shard. And then if you want to scale horizontally more you are just adding more shards and then MongoDB balancer will balance data across all those shards. There is also shard key which must be selected. This is not trivial operation but this talk is not about choosing short key. This way we are going to scale everything CPU memory storage reads and writes. Now let's have a look what is going on the hub level. DHA is running on Kubernetes. Currently we are using horizontal pot autoscaler. When CPU or memory threshold go above target, Kubernetes adds new pot automatically to handle this spike and scale them back down when traffic drops. Our deployment hub deployment can scale from 10 to 500 bots depends of on the traffic. This is how we keep the hub healthy without manual interventions and without infrastructure overprovisioning. So this is also cost effective solution. However, what happens if horizontal pot autoscaler want to add new ports but kubernetes does not have free nodes anymore. This is where second layer comes in. We are using cast AI for Kubernetes note autoscaling when pods are pending because there is no capacity and Kubernetes scheduler is not able to schedule them. Cast AAI is adding new nodes and then scheduler is able to schedule those spots. So we have two layers of scaling. First one is at deployment level. Second one is at infrastructure level via castai. But we are going to migrate horizontal port autoscaler to keta kubernetes eventdriven autoscaling. The difference HPA scale only based on CPU and memory. KDA scale on real application metrics like request per second or event loop utilization. It means scaling is driven by actual workload not by resource utilization only. For example, pot can have low CPU but high request Q. KDA can see it. HPA not the best part of this architecture. You never have to think about it. When you pop when you push the model, search the hub or download the model, it just works. This is what scaling medium models is really about. Keeping the user experience simple no matter how complex it gets under the hood. Thank you very much. It was pleasure for me to be here today and I wish nice day for all of you. Thank [applause] you.

Frontier News · by Hyperjump Technology