<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Victor May on Medium]]></title>
        <description><![CDATA[Stories by Victor May on Medium]]></description>
        <link>https://medium.com/@mayvic?source=rss-74065dfb1c25------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Stories by Victor May on Medium</title>
            <link>https://medium.com/@mayvic?source=rss-74065dfb1c25------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 20:39:14 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@mayvic/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[How I Published Three Papers This Year — Without a PhD or Research Job]]></title>
            <link>https://medium.com/@mayvic/intro-889c3e6e40b7?source=rss-74065dfb1c25------2</link>
            <guid isPermaLink="false">https://medium.com/p/889c3e6e40b7</guid>
            <category><![CDATA[research]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Victor May]]></dc:creator>
            <pubDate>Sat, 18 Oct 2025 21:01:01 GMT</pubDate>
            <atom:updated>2025-10-18T21:30:06.407Z</atom:updated>
            <content:encoded><![CDATA[<h3>How I Published Three Papers This Year — Without a PhD or Research Job</h3><h3>Intro</h3><p>A few years ago, I had what you could describe as a steady and unremarkable tech career. Master’s degree, a few MLE and data science jobs at midsize companies. Then one day I realized that my youth was vanishing in the rear-view mirror while I was not getting anywhere.</p><p><em>Friend: “Sorry I missed your call. I was giving my keynote at the Amalfi Coast Machine Learning Conference and then had a grant-funded dinner at a Michelin three-star restaurant with three Nobel laureates.”</em></p><p><em>Me: “Sorry I missed your text. I was failing the same CAPTCHA seven times while trying to log in to a mandatory compliance training on Python indentation.</em></p><p>Today, things look different. My first-author paper just got accepted to the prestigious ICSE conference — my <em>third</em> accepted paper this year. I guess it’s fair to say I’ve managed to break into research from what felt like an impossible position: a mid-career industry engineer, a decade out of grad school.</p><p>This post is about how that happened, and what I learned from it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/504/1*gCQVkOVJgtdsjzQk0BtM9A.png" /><figcaption>Climbing towards the ML research summit — one foothold at a time.</figcaption></figure><h3>Why Research?</h3><p>I was always excited about bridging the worlds of research and product. But I started my career on the product side of the house and mostly stayed there. I did on-calls, built data pipelines, engineered features, trained and deployed simple models to production. More recently, like everyone else, I spent a lot of time building on top of LLM APIs.</p><p>It was all good, honest work — but I missed the thrill of venturing into the unknown: trying out bold ideas, collaborating with people across the world, having public work artifacts. Instead — like many engineers — I often found myself in a loop of scrum sprints with well-intentioned project managers trying to “derisk” their teams’ work.</p><p>I wanted to spend more time doing research, but wanting to do research and knowing how to do it were very different things</p><h3>The Challenge</h3><p>Getting a paper accepted to a peer-reviewed venue is a high bar — especially with no PhD or prior research experience beyond my master’s thesis, which, to be honest, was already hard for me to get through. Unlike back then, I had no advisor, no lab, no community, and no free time. It wasn’t clear how to navigate any of it: idea development, scoping, submissions, rebuttals — all of it was uncharted territory.</p><h3>First Footholds</h3><p>I started small: I did a bit of work for the <a href="https://laion.ai/">LAION</a> OpenAssistant project, which led me to join the <a href="https://www.ontocord.ai/">Ontocord</a> community. It was a freewheeling affair, but it gave me my first real research foothold. We eventually published an LLM paper at COLING’s industry track. It wasn’t groundbreaking, but it proved that I could contribute meaningfully to a research collaboration and gave me motivation to pursue additional research projects.</p><h3>Things Started Moving</h3><p>For a time, my team was going through some changes, and I had more room for side projects. I signed up to contribute to a paper led by a collaborator I’d worked with at Ontocord. I also had an idea for a paper based on a project I did at work. Masochistically, I decided to do both. It turned out to be an incredibly hard stretch, but I powered through. Both papers were accepted to top-tier conferences.</p><p>Then another Ontocord project came along — again somewhat chaotic, but with interesting results that seemed worth writing up. I spent a decent amount of time polishing it and submitted that too. By the end of 2025, I had three accepted papers and one more still in review.</p><h3>Thinking in Terms of a Research Portfolio</h3><p>Now I have a growing backlog of ideas and some opportunities to join projects led by others. My next challenge is learning to think more strategically about shaping my research portfolio — how to balance risk and reward, match projects to the right venues, and build a broader network of collaborators to increase throughput.</p><p>To grow in these areas, I’m looking for mentorship from more experienced researchers. I’m also beginning to explore whether I can formalize this work within my organization — perhaps in a research engineer or a research scientist role.</p><h3>Learnings</h3><p>Looking back, one thing I kept as a priority was to focus my efforts where my comparative advantages actually were and look for help elsewhere, instead of trying to become good at everything. I’m not a great math person, but I can implement things quickly, coordinate people, and make projects move.</p><p>One thing that I did feel I have to dial in is the ability to think creatively and let my mind wander off the beaten path. A part of what helped me make progress is priming my mind to be open to ideas when they show up. Another was to avoid consuming too much content, as I found that it saturates my thinking and doesn’t leave space for exploration.</p><p>Leveraging the OSS community to latch on to more experienced researchers was a great idea. I made some long-term relationships that were and continue to be very fruitful. An experienced collaborator can help on prioritization, scoping and give valuable early feedback — these are very helpful for a beginner.</p><p>John Schulman’s <a href="http://joschu.net/blog/opinionated-guide-ml-research.html"><em>Opinionated Guide to ML Research</em></a> also left a mark on me, especially his distinction between idea-driven and goal-driven research. For me, goal-driven research turned out to be far more effective for generating ideas.</p><p>What surprised me most was how powerful it is to just keep showing up. Some of the people I’ve met who ended up having an impressive publication record weren’t the most technically brilliant — they were just willing to put in work, consistent and easy to collaborate with.</p><h3>Epilogue</h3><p>In retrospect, I realize that for far too long I’d been underestimating my own agency — my ability to steer my life in the direction I want, despite the circumstances — and especially the power of setting a goal and committing to it.</p><p>The goal I set for myself was to make it as a researcher, and eventually to turn it into a core part of my day job. I’ve already taken big steps toward that, despite all the difficulties, and I’m excited to see how far I can take it in 2026.</p><p>If you’re trying to start your own research journey from industry, I’d love to hear what’s worked for you.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=889c3e6e40b7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LLM Multi-GPU Batch Inference With Accelerate]]></title>
            <link>https://medium.com/@mayvic/llm-multi-gpu-batch-inference-with-accelerate-edadbef3e239?source=rss-74065dfb1c25------2</link>
            <guid isPermaLink="false">https://medium.com/p/edadbef3e239</guid>
            <category><![CDATA[large-language-models]]></category>
            <category><![CDATA[hugging-face]]></category>
            <category><![CDATA[inference]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Victor May]]></dc:creator>
            <pubDate>Sun, 10 Sep 2023 01:58:47 GMT</pubDate>
            <atom:updated>2023-09-10T12:09:58.661Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>Intro</strong></p><p>There are a lot of resources on how to optimize LLM inference for latency with a batch size of 1. This is important for the use-case of an end-user running a model locally for chat. However, I couldn’t find much on the subject of batch inference. Batch inference is important, for example, for evaluating models over large benchmarks.</p><p>In this blog, I’ll show how to use Hugging Face <a href="https://huggingface.co/docs/accelerate/index">Accelerate</a> to do batch inference. I’ll focus on a Multi-GPU setup, but a Multi-node setup should be pretty similar.</p><p><strong>Implementing the Inference Script</strong></p><p>The main flow of the batch inference script should look like roughly as follows:</p><pre>accelerator = Accelerator()<br><br>with accelerator.main_process_first():<br>  tokenizer = get_tokenizer(...)<br>  model = get_model(...)<br>  dataloader = get_dataloader(...)<br><br>model, dataloader = accelerator.prepare(model, dataloader)<br>output_sequences = run_generation(dataloader, tokenizer, model, accelerator)<br><br>accelerator.wait_for_everyone()<br><br>if accelerator.is_local_main_process:<br>  save_output(output_sequences, output_path)</pre><p>So what’s happening here? The Accelerator is the main class of the Accelerate library, so we need to create it. When we launch a script with Accelerate, it spawns a process for each GPU. The Accelerator class provides utilities to handle this multi-process workflow.</p><p>Now, let’s focus on each of the calls where the accelerator is referenced — everything else is standard HF Transformers stuff.</p><p>The purpose of the <em>with </em><a href="http://main_process_first/"><em>accelerator.main_process_first()</em></a><em> </em>context manager is to ensure that the code inside runs, well, on the main process first. Why is this important? For downloading datasets and models, obviously there is no point in downloading them separately on each worker process. For multi-worker CPU bound work, likewise the documentation <a href="http://Executing the Inference Script Assume for a moment that we already have an inference script">recommends</a> that will be done on the main process. Therefore, we load the model and the data and also do the tokenization within this context manager.</p><p>The <a href="https://huggingface.co/docs/accelerate/v0.22.0/en/package_reference/accelerator#accelerate.Accelerator.prepare"><em>accelerator.prepare()</em></a><em> </em>method prepares data and models for distributed processing by moving them to the GPU’s where they are needed. Therefore, there is no need to make calls such as <em>model.to(‘cuda’)</em>.</p><p>The <a href="http://main_process_first/"><em>accelerator.wait_for_everyone()</em></a><em> </em>call is done in order to ensure that all workers finished their part of the inference. When that is done, we can collect the output from all workers and create the output file. Conveniently, the output file will have the same order as the input dataset.</p><p>Now, let us take a look into the <em>run_generation() </em>method, which has a few more implementation details.</p><pre>def run_generation(dataloader, tokenizer, model, accelerator):<br>    model, dataloader = accelerator.prepare(model, dataloader)<br><br>    output_sequences = []<br><br>    for batch in tqdm(dataloader):<br>       with torch.inference_mode():<br>            generated_tokens = model.generate(**batch)<br><br>            generated_tokens = accelerator.pad_across_processes(<br>                generated_tokens, dim=1, pad_index=tokenizer.pad_token_id)<br><br>            generated_tokens = accelerator.gather_for_metrics(generated_tokens).cpu().tolist()<br><br>        outputs = [tokenizer.decode(ids, skip_special_tokens=True) for ids in generated_tokens]<br>        output_sequences.extend(outputs)<br><br>    return output_sequences</pre><p>Inside the inference loop, we make two calls to the accelerator: <a href="http://pad_across_processes/"><em>pad_across_processes</em></a> and <a href="http://gather_for_metrics/"><em>gather_for_metrics</em></a>. The latter’s purpose is to gather the outputs from the workers into the main process. The former is needed so that the batch sizes will be the same across all workers — the <em>gather_for_metrics </em>method requires that.</p><p><strong>Executing the Inference Script</strong></p><p>To start multi-GPU inference using Accelerate, you should be using the accelerate launch CLI. The command should look approximately as follows:</p><pre>accelerate launch \<br>--num_processes=2 \<br> generation.py \<br> --model_name [MODEL_NAME]\<br> --adapter_checkpoint_dir [ADAPTER_CHECKPOINT_DIR]\<br> --data_path [DATA_PATH]\<br> --output_dir [OUTPUT_DIR]</pre><p>Accelerate does have quite a bit of configuration options, so it’s a good idea to run accelerate config before starting an inference run.</p><p><strong>Conclusion</strong></p><p>We looked at how the Accelerate package from Hugging Face can be leveraged to implement a simple solution for multi-GPU inference. For convenience, I am providing a <a href="https://gist.github.com/mrcabbage972/d2cff4a0e856dd8e40e98d425f73802e">full working example script for Llama2</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=edadbef3e239" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Solving The Issue of Falcon Text Generation Never Stopping]]></title>
            <link>https://medium.com/@mayvic/solving-the-issue-of-falcon-text-generation-never-stopping-e8f599eae8f0?source=rss-74065dfb1c25------2</link>
            <guid isPermaLink="false">https://medium.com/p/e8f599eae8f0</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[large-language-models]]></category>
            <dc:creator><![CDATA[Victor May]]></dc:creator>
            <pubDate>Wed, 26 Jul 2023 01:49:37 GMT</pubDate>
            <atom:updated>2023-07-26T11:58:05.862Z</atom:updated>
            <content:encoded><![CDATA[<h3>Fine-Tuned Falcon-40b Model: Why It Never Stops Generating Text?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/1*J32XBSGEhHachH7Syp0qkw.png" /></figure><p><strong>Background</strong></p><p>Recently, I tried to fine-tune the <a href="https://huggingface.co/tiiuae/falcon-40b">Falcon-40b</a> model on a question answering dataset. To that end, I adapted some example Jupyter notebooks that are available on the web, e.g. <a href="https://huggingface.co/blog/falcon#fine-tuning-with-peft">here</a> and <a href="https://github.com/aws-samples/amazon-sagemaker-generativeai/blob/main/studio-notebook-fine-tuning/falcon-40b-qlora-finetune-summarize.ipynb">here</a>.</p><p>To my surprise, the fine-tuned model couldn’t quite finish its answers — it usually kept generating tokens until it hit the max_tokens limit. The issue turned out to be specific to Falcon models — it didn’t happen for example when I swapped the base model to OpenLlama.</p><p>In theory, this is not supposed to happen because training examples have a prompt format such as <em>“{question}&lt;separator&gt;{answer}&lt;eos token&gt;”. </em>The model should learn that the end-of-sequence token has a high probability when an answer ends. However, this wasn’t happening in my experiments.</p><p><strong>Diagnosing The Problem</strong></p><p>Googling the issue has led to a couple of relevant threads (<a href="https://github.com/huggingface/blog/issues/1302">1</a>, <a href="https://github.com/huggingface/transformers/issues/22794">2</a>). From these, it became clear that the problem is with the padding token, which the Falcon actually doesn’t have. This is a problem because we need to pad input examples to create batches for training. The way that the example notebooks deal with this is by setting:</p><pre>tokenizer.pad_token = tokenizer.eos_token</pre><p>In theory, this should be fine because the pad tokens should get masked out, while the real end-of-sentence token should not get masked out.</p><p>The problem is that when fine-tuning the model according to the recipe suggested by the tutorials, we collate the input with <a href="https://huggingface.co/docs/transformers/main_classes/data_collator#transformers.DataCollatorForLanguageModeling"><em>DataCollatorForLanguageModeling</em></a>. This collator applies the following logic:</p><pre>if self.tokenizer.pad_token_id is not None:<br> labels[labels == self.tokenizer.pad_token_id] = -100</pre><p>As we know from the <a href="https://pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html">PyTorch doc</a>, labels that equal -100 are excluded from the loss calculation. Our pad token being same as the end-of-sequence token, that means that the model never learns to finish generating responses.</p><p><strong>Solutions</strong></p><p>One way to mitigate this issue is, of course, to set the pad token to something else. For example, create a new special token:</p><pre>tokenizer.add_special_tokens({&#39;pad_token&#39;: &#39;[PAD]&#39;})<br>with torch.no_grad():<br>  model.resize_token_embeddings(len(tokenizer))<br>model.config.pad_token_id = tokenizer.pad_token_id</pre><p>In the snippet above, the last line is needed for <em>the model.generate() </em>method to use the correct padding token — otherwise it defaults to use the end-of-sentence token. One extra comment: it’s probably a good idea to set the new token’s weight to the mean of all existing token embedding, as discussed <a href="https://nlp.stanford.edu/~johnhew/vocab-expansion.html">here</a>.</p><p>Another solution is to use a different collator that doesn’t handle the padding token in the same way. Since I was working on question answering, the <em>DataCollatorForSeq2Seq </em>collator was a natural candidate. With this collator, there are two options:</p><ol><li>Treat the question as the input and the labels as the output. Then, the loss is calculated only over the labels.</li><li>Treat the whole prompt as both the input and the output.</li></ol><p>I didn’t experiment with which of the two options leads to better results, as that was out of scope at the time. I did implement the 2nd option, roughly as in the snippet below:</p><pre>def tokenize(prompt, max_length=1024):<br>    result = tokenizer(<br>        prompt,<br>        truncation=True,<br>        max_length=cutoff_len,<br>        padding=False,<br>        return_tensors=None,<br>    )<br>    result[&quot;labels&quot;] = result[&quot;input_ids&quot;].copy()<br>    return result<br><br>tokenized_dataset = dataset.map(tokenize, remove_columns=list(dataset.features))<br><br>data_collator = transformers.DataCollatorForSeq2Seq(<br>                  tokenizer,                  <br>                  return_tensors=&quot;pt&quot;,<br>                  padding=True)<br>      <br>trainer = transformers.Trainer(<br>    model=model,<br>    train_dataset=tokenized_dataset,<br>    data_collator=data_collator<br>)</pre><p><strong>Conclusion</strong></p><p>In this blog post, we addressed the issue of the Falcon model indefinitely generating tokens until it hits the max token limit. We diagnosed that the issue stemmed from the model’s lack of a unique padding token which resulted in the model’s inability to learn when to end responses. We proposed two potential solutions: Firstly, creating a new special token to act as the padding token, and secondly, using a different collator that does not handle padding tokens in the same way as DataCollatorForLanguageModeling.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e8f599eae8f0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Scalable Streaming of OpenAI Model Responses with FastAPI and asyncio]]></title>
            <link>https://medium.com/@mayvic/scalable-streaming-of-openai-model-responses-with-fastapi-and-asyncio-714744b13dd?source=rss-74065dfb1c25------2</link>
            <guid isPermaLink="false">https://medium.com/p/714744b13dd</guid>
            <category><![CDATA[rest-api]]></category>
            <category><![CDATA[large-language-models]]></category>
            <category><![CDATA[python-programming]]></category>
            <category><![CDATA[fastapi]]></category>
            <dc:creator><![CDATA[Victor May]]></dc:creator>
            <pubDate>Thu, 13 Jul 2023 15:39:22 GMT</pubDate>
            <atom:updated>2023-07-13T16:22:17.462Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>Intro</strong></p><p>A common use-case for LLM-based applications is an API server that makes a call to an LLM API, does some processing on the response and returns it to the caller. Since LLM API calls can be very slow, a common method to improve the user experience is to use a <a href="https://github.com/openai/openai-cookbook/blob/main/examples/How_to_stream_completions.ipynb">streaming API</a>. In this post, I will show how to build a scalable API service that serves streaming and potentially post-processed LLM API responses.</p><p><strong>Background</strong></p><p>FastAPI is a modern and reliable framework for building REST API’s. However, due to the Python <a href="https://realpython.com/python-gil/">GIL</a>, a single process of a FastAPI or similar Python service can handle only one request at a time, making the service <a href="https://rachelbythebay.com/w/2020/03/07/costly/">difficult to scale</a>.</p><p>However, in our use-case the bottleneck for a Python REST API service is the I/O time of calling an OpenAI model. This allows us to get around the GIL issue via concurrency, as multiple requests can all wait for their output to arrive simultaneously. This can be achieved in a few ways, such as good old thread-pools or the more modern concept of co-routines, which is implemented in Python by the <a href="https://docs.python.org/3/library/asyncio.html">asyncio</a> package. The latter is <a href="https://realpython.com/python-concurrency/">better suited</a> for scaling the I/O-bound use-case as co-routines are lighter than system threads.</p><p><strong>API Server Implementation</strong></p><p>Given that there is any number of other resources on <a href="https://fastapi.tiangolo.com/tutorial/first-steps/">how to create</a> a boilerplate FastAPI application, let us focus on the parts that are specific to the task at hand.</p><p>First, we will define the input schema for our API endpoint using Pydantic. More details on this can be found for example h<a href="https://fastapi.tiangolo.com/tutorial/body/">ere</a>.</p><pre>from pydantic import BaseModel<br><br>class InferenceRequest(BaseModel):<br>    model_name: str<br>    input_text: str <br>    api_key: Optional[str]<br>    org_id: Optional[str] = None<br>    generation_cfg: Optional[dict] = None</pre><p>Now, let us define the handler of the API endpoint.</p><pre>from fastapi import FastAPI, HTTPException<br>from starlette.responses import StreamingResponse<br>import openai<br><br>app = FastAPI()<br><br>@app.post(f&quot;/openai_streaming&quot;)<br>async def openai_streaming(request: InferenceRequest) -&gt; StreamingResponse: <br>  try:    <br>      subscription = await openai.ChatCompletion.acreate(<br>          model=request.model_name,<br>          api_key=request.api_key,<br>          organization=request.org_id,<br>          messages=text,<br>          stream=True,<br>          **request.generation_cfg<br>      )<br>  <br>      return StreamingResponse(stream_generator(subscription),<br>                               media_type=&#39;text/event-stream&#39;)<br>  except openai.OpenAIError:<br>    raise HTTPException(status_code=500, detail=&#39;OpenAI call failed&#39;)</pre><p>A couple of things to note here:</p><ol><li>We are using the async version of the OpenAI ChatCompletion.</li><li>In order to return a stream of <a href="https://devdojo.com/bobbyiliev/how-to-use-server-sent-events-sse-with-fastapi">Server-Sent Events</a> , our handler method returns an object of type StreamingResponse.</li></ol><p>Now, we will create the <em>stream_generator</em> method.</p><pre>import async_timeout<br>import asyncio<br><br>GENERATION_TIMEOUT_SEC = 60<br><br>async def stream_generator(subscription):<br>    async with async_timeout.timeout(GENERATION_TIMEOUT_SEC):<br>        try:<br>            async for chunk in subscription:<br>                yield post_processing(chunk)<br>        except asyncio.TimeoutError:<br>            raise HTTPException(status_code=504, detail=&quot;Stream timed out&quot;)</pre><p>Here, the <em>post_processing</em> method contains whatever business logic that needs to be applied to each data chunk. Of course, this code could be modified to buffer multiple chunks and post-process them jointly and so forth.</p><p>Its is important to have the <em>async_timeout.timeout </em>in the generator to ensure that the method yields. Otherwise, it can get stuck indefinitely and leave the client hanging.</p><p><strong>Client Implementation</strong></p><p>For completeness, here’s an example client implementation using the <a href="https://www.python-httpx.org/">httpx</a> package.</p><pre>import httpx<br><br>from inference_request import InferenceRequest<br><br>ENDPOINT_URL = &#39;http://0.0.0.0:80/openai_streaming&#39;<br><br>payload = InferenceRequest(key=&#39;MY_API_KEY&#39;, <br>                           input_text=&#39;What is the capital of Italy?&#39;,<br>                           model_name=&#39;gpt-4&#39;).dict()<br><br>with httpx.stream(&#39;POST&#39;, ENDPOINT_URL, json=payload) as r:<br>     for chunk in r.iter_raw():        <br>         print(chunk)</pre><p><strong>Benchmarking</strong></p><p>In my experiments, I clocked about 10k requests per minute when hosting a single worker of the service (using <a href="https://www.uvicorn.org/">uvicorn</a>) on a machine that has a single CPU and 2gb RAM.</p><p><strong>Conclusion</strong></p><p>FastAPI, when combined with asyncio, can provide a robust solution for building high-performance streaming applications leveraging OpenAI models or other LLM API’s.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=714744b13dd" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>