![Revisit consent button](../images/body-df29beacd7496220cc94.svg)
We value your privacy
We use cookies to enhance your browsing experience, serve personalised ads or content, and analyse our traffic. By clicking "Accept All", you consent to our use of cookies.
Customise Reject All Accept All
Customise Consent Preferences ![](../images/body-2159bb67f3de8ad870eb.svg)
We use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.
The cookies that are categorised as "Necessary" are stored on your browser as they are essential for enabling the basic functionalities of the site. ... Show more
NecessaryAlways Active
Necessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.
  * Cookie
BCTempID
  * Duration
10 minutes
  * Description
No description available.


  * Cookie
__cf_bm
  * Duration
1 hour
  * Description
This cookie, set by Cloudflare, is used to support Cloudflare Bot Management. 


  * Cookie
AWSALBCORS
  * Duration
7 days
  * Description
Amazon Web Services set this cookie for load balancing.


  * Cookie
_cfuvid
  * Duration
session
  * Description
Cloudflare sets this cookie to track users across sessions to optimize user experience by maintaining session consistency and providing personalized services


  * Cookie
li_gc
  * Duration
6 months
  * Description
Linkedin set this cookie for storing visitor's consent regarding using cookies for non-essential purposes.


  * Cookie
__hssrc
  * Duration
session
  * Description
This cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.


  * Cookie
__hssc
  * Duration
1 hour
  * Description
HubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.


  * Cookie
wpEmojiSettingsSupports
  * Duration
session
  * Description
WordPress sets this cookie when a user interacts with emojis on a WordPress site. It helps determine if the user's browser can display emojis properly.


  * Cookie
BCSessionID
  * Duration
1 year 1 month 4 days
  * Description
Blueconic sets this cookie as a unique identifier for the BlueConic profile.


  * Cookie
_octo
  * Duration
1 year
  * Description
No description available.


  * Cookie
logged_in
  * Duration
1 year
  * Description
No description available.


  * Cookie
__Secure-YEC
  * Duration
past
  * Description
YouTube sets this cookie to stores the user's video player preferences using embedded YouTube video


  * Cookie
__eoi
  * Duration
6 months
  * Description
Description is currently not available.


  * Cookie
AWSALBTGCORS
  * Duration
7 days
  * Description
No description available.


  * Cookie
login-status-p
  * Duration
past
  * Description
Description is currently not available.


  * Cookie
AWSALBTG
  * Duration
7 days
  * Description
No description available.


  * Cookie
csrf_token
  * Duration
session
  * Description
No description available.


  * Cookie
token_v2
  * Duration
1 day
  * Description
Description is currently not available.


  * Cookie
D
  * Duration
1 year
  * Description
Description is currently not available.


  * Cookie
PHPSESSID
  * Duration
session
  * Description
This cookie is native to PHP applications. The cookie stores and identifies a user's unique session ID to manage user sessions on the website. The cookie is a session cookie and will be deleted when all the browser windows are closed.


  * Cookie
VISITOR_PRIVACY_METADATA
  * Duration
6 months
  * Description
YouTube sets this cookie to store the user's cookie consent state for the current domain. 


  * Cookie
cookietest
  * Duration
session
  * Description
The cookietest cookie is typically used to determine whether the user's browser accepts cookies, essential for website functionality and user experience.


  * Cookie
__Host-airtable-session
  * Duration
1 year
  * Description
This cookie is used to enable us to integrate the services of Airtable.


  * Cookie
__Host-airtable-session.sig
  * Duration
1 year
  * Description
This cookie is used to enable us to integrate the services of Airtable.


  * Cookie
m
  * Duration
1 year 1 month 4 days
  * Description
Stripe sets this cookie for fraud prevention purposes. It identifies the device used to access the website, allowing the website to be formatted accordingly.


  * Cookie
BIGipServer*
  * Duration
session
  * Description
Marketo sets this cookie to collect information about the user's online activity and build a profile about their interests to provide advertisements relevant to the user. 


  * Cookie
__cfruid
  * Duration
session
  * Description
Cloudflare sets this cookie to identify trusted web traffic.


  * Cookie
_GRECAPTCHA
  * Duration
6 months
  * Description
Google Recaptcha service sets this cookie to identify bots to protect the website against malicious spam attacks.


  * Cookie
__Secure-YNID
  * Duration
6 months
  * Description
Google cookie used to protect user security and prevent fraud, especially during the login process.


  * Cookie
cookieyes-consent
  * Duration
1 year
  * Description
CookieYes sets this cookie to remember users' consent preferences so that their preferences are respected on subsequent visits to this site. It does not collect or store any personal information about the site visitors.


Functional
Functional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.
  * Cookie
lidc
  * Duration
1 day
  * Description
LinkedIn sets the lidc cookie to facilitate data center selection.


  * Cookie
brw
  * Duration
1 year
  * Description
No description available.


  * Cookie
brwConsent
  * Duration
5 minutes
  * Description
Description is currently not available.


  * Cookie
WMF-Uniq
  * Duration
1 year
  * Description
Description is currently not available.


  * Cookie
loom_anon_comment
  * Duration
1 year
  * Description
No description available.


  * Cookie
loom_referral_video
  * Duration
session
  * Description
Description is currently not available.


  * Cookie
VISITOR_INFO1_LIVE
  * Duration
6 months
  * Description
A cookie set by YouTube to measure bandwidth that determines whether the user gets the new or old player interface.


  * Cookie
yt-remote-connected-devices
  * Duration
Never Expires
  * Description
YouTube sets this cookie to store the user's video preferences using embedded YouTube videos.


  * Cookie
ytidb::LAST_RESULT_ENTRY_KEY
  * Duration
Never Expires
  * Description
The cookie ytidb::LAST_RESULT_ENTRY_KEY is used by YouTube to store the last search result entry that was clicked by the user. This information is used to improve the user experience by providing more relevant search results in the future.


  * Cookie
yt-remote-device-id
  * Duration
Never Expires
  * Description
YouTube sets this cookie to store the user's video preferences using embedded YouTube videos.


  * Cookie
yt-remote-session-name
  * Duration
session
  * Description
The yt-remote-session-name cookie is used by YouTube to store the user's video player preferences using embedded YouTube video.


  * Cookie
yt-remote-fast-check-period
  * Duration
session
  * Description
The yt-remote-fast-check-period cookie is used by YouTube to store the user's video player preferences for embedded YouTube videos.


  * Cookie
yt-remote-session-app
  * Duration
session
  * Description
The yt-remote-session-app cookie is used by YouTube to store user preferences and information about the interface of the embedded YouTube video player.


  * Cookie
yt-remote-cast-available
  * Duration
session
  * Description
The yt-remote-cast-available cookie is used to store the user's preferences regarding whether casting is available on their YouTube video player.


  * Cookie
yt-remote-cast-installed
  * Duration
session
  * Description
The yt-remote-cast-installed cookie is used to store the user's video player preferences using embedded YouTube video.


  * Cookie
cp_session
  * Duration
3 months
  * Description
Codepen sets this cookie for Help systems found in the website.


  * Cookie
loid
  * Duration
1 year 1 month 4 days
  * Description
This cookie is set by the Reddit. The cookie enables the sharing of content from the website onto the social media platform.


Analytics
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.
  * Cookie
__hstc
  * Duration
6 months
  * Description
Hubspot set this main cookie for tracking visitors. It contains the domain, initial timestamp (first visit), last timestamp (last visit), current timestamp (this visit), and session number (increments for each subsequent session).


  * Cookie
hubspotutk
  * Duration
6 months
  * Description
HubSpot sets this cookie to keep track of the visitors to the website. This cookie is passed to HubSpot on form submission and used when deduplicating contacts.


  * Cookie
_ga
  * Duration
1 year 1 month 4 days
  * Description
Google Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors.


  * Cookie
_ga_*
  * Duration
1 year 1 month 4 days
  * Description
Google Analytics sets this cookie to store and count page views.


  * Cookie
__Host-psifi.analyticsTrace
  * Duration
6 hours
  * Description
Description is currently not available.


  * Cookie
__Host-psifi.analyticsTraceV2
  * Duration
6 hours
  * Description
Description is currently not available.


  * Cookie
_gh_sess
  * Duration
session
  * Description
GitHub sets this cookie for temporary application and framework state between pages like what step the user is on in a multiple step form.


  * Cookie
YSC
  * Duration
session
  * Description
YSC cookie is set by Youtube and is used to track the views of embedded videos on Youtube pages.


  * Cookie
ajs_anonymous_id
  * Duration
1 year
  * Description
This cookie is set by Segment to count the number of people who visit a certain site by tracking if they have visited before.


  * Cookie
vuid
  * Duration
1 year 1 month 4 days
  * Description
Vimeo installs this cookie to collect tracking information by setting a unique ID to embed videos on the website. 


Performance
Performance cookies are used to understand and analyse the key performance indexes of the website which helps in delivering a better user experience for the visitors.
  * Cookie
AWSALB
  * Duration
7 days
  * Description
AWSALB is an application load balancer cookie set by Amazon Web Services to map the session to the target.


  * Cookie
acq
  * Duration
past
  * Description
Description is currently not available.


  * Cookie
acq.sig
  * Duration
past
  * Description
Description is currently not available.


  * Cookie
ptc
  * Duration
2 years
  * Description
No description available.


Advertisement
Advertisement cookies are used to provide visitors with customised advertisements based on the pages you visited previously and to analyse the effectiveness of the ad campaigns.
  * Cookie
muc_ads
  * Duration
1 year 1 month 4 days
  * Description
Twitter sets this cookie to collect user behaviour and interaction data to optimize the website.


  * Cookie
guest_id_marketing
  * Duration
1 year 1 month 4 days
  * Description
Twitter sets this cookie to identify and track the website visitor.


  * Cookie
guest_id_ads
  * Duration
1 year 1 month 4 days
  * Description
Twitter sets this cookie to identify and track the website visitor.


  * Cookie
personalization_id
  * Duration
1 year 1 month 4 days
  * Description
Twitter sets this cookie to integrate and share features for social media and also store information about how the user uses the website, for tracking and targeting.


  * Cookie
guest_id
  * Duration
1 year 1 month 4 days
  * Description
Twitter sets this cookie to identify and track the website visitor. It registers if a user is signed in to the Twitter platform and collects information about ad preferences.


  * Cookie
bcookie
  * Duration
1 year
  * Description
LinkedIn sets this cookie from LinkedIn share buttons and ad tags to recognize browser IDs.


  * Cookie
__Secure-ROLLOUT_TOKEN
  * Duration
6 months
  * Description
YouTube sets this cookie to manage feature rollout and experimentation. It helps Google control which new features or interface changes are shown to users as part of testing and staged rollouts, ensuring consistent experience for a given user during an experiment.


  * Cookie
yt.innertube::nextId
  * Duration
Never Expires
  * Description
YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.


  * Cookie
yt.innertube::requests
  * Duration
Never Expires
  * Description
YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.


  * Cookie
session_tracker
  * Duration
session
  * Description
This cookie is set by the Reddit. This cookie is used to identify trusted web traffic. It also helps in adverstising on the website.


  * Cookie
edgebucket
  * Duration
session
  * Description
Reddit sets this cookie to save the information about a log-on Reddit user, for the purpose of advertisement recommendations and updating the content.


  * Cookie
did
  * Duration
1 year
  * Description
Arbor sets this cookie to show targeted ads to site visitors.This cookie expires after 2 months or 1 year.


Uncategorised
Other uncategorised cookies are those that are being analysed and have not been classified into a category as yet.
No cookies to display.
Reject All Save My Preferences Accept All
Toggle Search[](https://towardsdatascience.com/)Toggle Mobile Navigation
[](https://towardsdatascience.com/)
Publish AI, ML & data-science insights to a global community of data professionals.
Sign in[Submit an Article](https://write.insightmediagroup.io)
[Latest](https://towardsdatascience.com/latest/)[Editor's Picks](https://towardsdatascience.com/tag/editors-pick/)[Newsletter](https://towardsdatascience.com/tag/the-variable/)[ShipAI](https://towardsdatascience.com/shipai/)
* * *
[Write For TDS](https://towardsdatascience.com/submissions/)
[LinkedIn](https://www.linkedin.com/company/towards-data-science/)[X](https://x.com/TDataScience)Toggle Search
[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)
# Optimizing Token Generation in PyTorch Decoder Models
Hiding host-device synchronization via CUDA stream interleaving
[Chaim Rand](https://towardsdatascience.com/author/chaimrand/)
February 24, 202615 min read
![](../images/000.jpeg)
[Photo](https://unsplash.com/photos/time-lapse-photography-of-river--qrcOR33ErA) by Hendrik Cornelissen on Unsplash
Underlying the large language models that have pervaded nearly every facet of our daily lives are autoregressive decoder models. These models apply compute-heavy kernel operations to churn out tokens one by one in a manner that, at first glance, seems extremely inefficient. Given the enormous demand for generative AI, it is no surprise that extraordinary engineering effort is being invested into its optimization. Whether it be through custom CUDA kernels, CUDA Graphs, dedicated AI accelerators, or [speculative sampling ](https://medium.com/@xiaxiami/speculative-sampling-in-llms-speeding-up-inference-with-drafts-verification-parallelism-6d948d268a87)— any technique that reduces latency and/or cost by even a fraction of a percentage is a win.
In this post, we demonstrate a technique for optimizing token generation in PyTorch using CUDA stream interleaving. While simple to implement, the method addresses a specific, often overlooked bottleneck and can lead to meaningful performance boosts. While pipelining model execution using CUDA streams is common in AI systems engineering, we did not find any tutorial documenting the specific PyTorch-level application we describe here. If you find the technique useful, please be so kind as to reference this post.
To facilitate our discussion, we will use a simple GPT-2 PyTorch decoder model from HuggingFace’s transformers (v5.1.0) library. We will run our experiments on an [NVIDIA L40S](https://www.nvidia.com/en-eu/data-center/l40s/) GPU and [PyTorch (2.10.0)](https://pytorch.org/get-started/locally/).
**Disclaimer:** The code we will share is intended for demonstrative purposes. Please do not rely on its accuracy or optimality. Please do not interpret our mentions of any library, platform, or service as an endorsement of its use.
Importantly, the value of the CUDA stream-based method we will discuss can vary greatly based on the details of your model and runtime environment. Please be sure to run your own benchmarks before integrating its use.
Our focus in this post is on PyTorch-native inference workloads which remain extremely prevalent in development and test settings. However, it is important to note that for production environments dedicated LLM inference libraries such as vLLM or NVIDIA TensorRT-LLM tend to deliver greater performance and should be used whenever relevant.
Learn this step by step with the interactive [AI Engineer](https://roadmap.sh/ai-engineer) roadmap.
## A Toy GPT-2 Model
To simplify our discussion, we will use a GPT-2 decoder model from the HuggingFace `transformers` library and have it run autoregressively on a batch of empty prompts.
In the following code block, we initialize the model and define a naive token generation function that creates a batch of random streams up to a given length.
python

```
import torch
from transformers import GPT2LMHeadModel, GPT2Config

torch.set_float32_matmul_precision('high')

DEVICE = "cuda"

# define the decoder model
config = GPT2Config.from_pretrained("gpt2")
model = GPT2LMHeadModel(config).to(DEVICE).eval()


@torch.inference_mode()
def generate_sequence(model, max_seqlen, batch_size):
    # Initialize prompts with BOS token
    all_tokens = torch.full(
        (batch_size, 1),
        config.bos_token_id,
        device=DEVICE,
        dtype=torch.long
    )
    finished = torch.zeros(batch_size, device=DEVICE, dtype=torch.bool)
    
    for i in range(max_seqlen):
        outputs = model(all_tokens)
        # extract new token
        logits = outputs.logits[:, -1, :]
        new_tokens = torch.argmax(logits, dim=-1)
        # append new token to sequence
        all_tokens = torch.cat(
            [all_tokens, new_tokens.unsqueeze(-1)],
            dim=-1
        )
        finished |= (new_tokens == config.eos_token_id)
        stop_gpu = torch.all(finished)
        
        # checking stop condition
        if stop_gpu.item():
            print(f"All sequences finished at step {i+1}")
            break
    
    return all_tokens
```

Next, we define a simple benchmarking function which we use to measure the runtime performance and memory utilization of our token generator in different scenarios.
python

```
import time, statistics


def benchmark(func, num_runs=10):
    # Warmup
    func()
    torch.cuda.synchronize()
    
    runtimes = []
    
    for _ in range(num_runs):
        # reset memory stats before each run
        torch.cuda.empty_cache()
        torch.cuda.reset_peak_memory_stats()
        torch.cuda.synchronize()
        
        start = time.perf_counter()
        _ = func()
        torch.cuda.synchronize()
        end = time.perf_counter()
        
        runtimes.append(end - start)
    
    # Get memory allocator stats from last run
    mem_stats = torch.cuda.memory_stats()
    allocated_peak = mem_stats.get('allocated_bytes.all.peak', 0)
    reserved_peak = mem_stats.get('reserved_bytes.all.peak', 0)
    f_peak = reserved_peak - allocated_peak
    f_pct = (
        100 * f_peak / reserved_peak
        if reserved_peak > 0 else 0
    )
    
    print(f"\n{'='*60}")
    print(f"Runtime Results:")
    print(f" Mean:               {statistics.mean(runtimes):.4f}s")
    print(f" Std:                {statistics.stdev(runtimes):.4f}s")
    print(f" Min:                {min(runtimes):.4f}s")
    print(f" Max:                {max(runtimes):.4f}s")

    print(f"\nMemory Stats:")
    print(f" Allocated bytes (peak): {allocated_peak / 1e9:.3f} GB")
    print(f" Reserved bytes (peak):  {reserved_peak / 1e9:.3f} GB")
    print(f" Fragmentation (peak):   {f_peak / 1e9:.3f} GB ({f_pct:.1f}%)")
    print(f"{'='*60}\n")


batch_size = 32
for max_seqlen in [100, 200, 400]:
    print(
        f"Benchmarking generation with batch size {batch_size} "
        f"and max sequence length {max_seqlen}..."
    )
    benchmark(
        lambda: generate_sequence(
            model, max_seqlen=max_seqlen, batch_size=batch_size
        )
    )
```

In the table below we capture the results for a batch size of 32 and several different sequence lengths:
![](../images/001.webp)
Baseline Results (By Author)
As the sequence length doubles, the runtime quadruples — appearing to follow a classic  _O(N²)_ scaling pattern. Additionally, high memory fragmentation points to severe strain on the CUDA memory allocator, which can result in frequent memory faults and degrade runtime performance. The fragmentation results from each step asking for slightly larger tensor allocations, a pattern which ends up leaving multiple pockets of unusable memory.
Our first optimization, KV caching, addresses the runtime complexity of our decoder model.
### KV Caching
Our naive generator is extremely inefficient — rather than storing and reusing the intermediate tensors from previous tokens, it recalculates the entire sequence at every step.
We address the computation inefficiency by using KV caching: We store and reuse the intermediate Key and Value tensors for previous tokens. KV caching reduces the runtime complexity of token generation from  _O(N²)_ to  _O(N)_.
In the following code block, we utilize the transformers library’s built-in support for KV caching to reprogram our token generation function to compute a single batch of tokens in each step.
python

```
@torch.inference_mode()
def generate_sequence(model, max_seqlen, batch_size, use_cache=False):
    # Initialize prompts with BOS token
    all_tokens = torch.full(
        (batch_size, 1),
        config.bos_token_id,
        device=DEVICE,
        dtype=torch.long
    )
    finished = torch.zeros(batch_size, device=DEVICE, dtype=torch.bool)

    # past_key_values is used to store the cached key/values for each layer
    past_key_values = None

    for i in range(max_seqlen):
        current_input = (
            all_tokens if past_key_values is None
            else all_tokens[:, -1:]
        )
        outputs = model(
            current_input,
            past_key_values=past_key_values,
            use_cache=use_cache
        )
        # update cache for next step
        past_key_values = outputs.past_key_values
        logits = outputs.logits[:, -1, :]
        new_tokens = torch.argmax(logits, dim=-1)
        # append new token to sequence
        all_tokens = torch.cat(
            [all_tokens, new_tokens.unsqueeze(-1)],
            dim=-1
        )
        finished |= (new_tokens == config.eos_token_id)
        stop_gpu = torch.all(finished)
        
        # checking stop condition
        if stop_gpu.item():
            print(f"All sequences finished at step {i+1}")
            break
    
    return all_tokens
```

The resulting performance numbers are captured in the following table:
![](../images/body-08e7448ed118a0c17ea2.webp)
Token Generation With KV Caching (By Author)
The performance improvement is profound and, as expected, increases as a function of the sequence length.
Although somewhat better than in our baseline experiment, the degree of memory fragmentation remains a concern. To address this we explore two methods, expandable memory allocations and static KV caching.
### Expandable CUDA Memory Allocations
To reduce CUDA memory fragmentation, we program PyTorch to use [expandable memory segments](https://docs.pytorch.org/docs/stable/notes/cuda.html#optimizing-memory-usage-with-pytorch-alloc-conf). As of the time of this writing, this memory optimization is an experimental feature and should be used with caution. Please see [the PyTorch documentation](https://docs.pytorch.org/docs/stable/notes/cuda.html#optimizing-memory-usage-with-pytorch-alloc-conf) for details. To use the feature we set the following environment variable:
bash

```
export PYTORCH_ALLOC_CONF="expandable_segments:True"
```

Rerunning our benchmark results in the following table:
![](../images/body-4e4b721cbe74d2c51ae2.webp)
KV Caching With Expandable Memory Segments (By Author)
Not only do we see a marked improvement in fragmentation, but we also get an additional (marginal) improvement in runtime performance.
### KV Caching With StaticCache
The [default cache](https://huggingface.co/docs/transformers/en/kv_cache#default-cache) in HuggingFace is dynamic — it grows as the number of keys and values increases during the generation progresses. HuggingFace supports a fixed-size cache, [StaticCache](https://huggingface.co/docs/transformers/v5.1.0/en/internal/generation_utils#transformers.StaticCache), which pre-allocates a maximum cache size for the KV pairs and reduces strain on the CUDA memory allocator. The disadvantage of using [StaticCache](https://huggingface.co/docs/transformers/v5.1.0/en/internal/generation_utils#transformers.StaticCache) is that the full length of the cache participates in the attention computation at each token generation step, where irrelevant tokens are masked out. This results in a waste of computation that grows with the sequence length. For example, when generating a sequence of 400 tokens, the attention computation for each token will be run on full  _400_ X _400_ -sized tensors.
In the code block below we enhance our sequence generator to support the use of a [StaticCache](https://huggingface.co/docs/transformers/v5.1.0/en/internal/generation_utils#transformers.StaticCache):
python

```
che:

from transformers import StaticCache

@torch.inference_mode()
def generate_sequence(
    model, max_seqlen, batch_size, use_cache=False, use_static_cache=False
):
    # Initialize prompts with BOS token
    all_tokens = torch.full(
        (batch_size, 1),
        config.bos_token_id,
        device=DEVICE,
        dtype=torch.long
    )
    finished = torch.zeros(batch_size, device=DEVICE, dtype=torch.bool)
    
    # Initialize static cache if requested
    if use_cache and use_static_cache:
        past_key_values = StaticCache(
            config=config,
            max_batch_size=batch_size,
            max_cache_len=max_seqlen,
            device=DEVICE,
            dtype=model.dtype
        )
    else:
        past_key_values = None
    
    # Initialize cache position tracking for static cache
    cache_positions = torch.arange(max_seqlen, device=DEVICE)
    
    for i in range(max_seqlen):
        current_input = (
            all_tokens if past_key_values is None
            else all_tokens[:, -1:]
        )
        cache_position = (
            cache_positions[i:i+1] if use_static_cache else None
        )
        outputs = model(
            current_input,
            past_key_values=past_key_values,
            cache_position=cache_position,
            use_cache=use_cache
        )
        # update cache for next step
        past_key_values = outputs.past_key_values
        logits = outputs.logits[:, -1, :]
        new_tokens = torch.argmax(logits, dim=-1)
        # append new token to sequence
        all_tokens = torch.cat(
            [all_tokens, new_tokens.unsqueeze(-1)],
            dim=-1
        )
        finished |= (new_tokens == config.eos_token_id)
        stop_gpu = torch.all(finished)
        
        # checking stop condition
        if stop_gpu.item():
            print(f"All sequences finished at step {i+1}")
            break
    
    return all_tokens
```

The updated results are captured below:
![](../images/body-26e1fd4c3df70006476e.webp)
Token Generation With Static KV Cache (By Author)
Using a fixed-sized cache greatly improves memory utilization as indicated by the decrease in memory fragmentation. However, its impact on runtime performance is mixed — for 100 tokens it reduces performance compared to a dynamic cache, whereas for 200 and 400 tokens it boosts performance by 9% and 10%, respectively.
There are more advanced methods of implementing attention that optimize for memory utilization without the cost of wasted computation. In a previous post, [Optimizing Transformer Models for Variable-Length Input Sequences](https://towardsdatascience.com/optimizing-transformer-models-for-variable-length-input-sequences-19fb88fddf71/), we covered some PyTorch techniques for computing attention  _sparsely_ to reduce computation waste. For production settings, libraries such as vLLM use [PagedAttention](https://blog.vllm.ai/2023/06/20/vllm.html) for maximizing memory utilization. These methods are outside the scope of this post.
For more details on caching in HuggingFace, please see the [caching strategies overview](https://huggingface.co/docs/transformers/en/kv_cache#cache-strategies).
### Model Compilation
One of the [documented](https://huggingface.co/docs/transformers/en/kv_cache#fixed-size-cache) advantages of using a fixed-sized cache is that it allows for taking advantage of many just-in-time (JIT) optimizations.
In the following code block we apply our benchmark to a [PyTorch-compiled](https://docs.pytorch.org/tutorials/intermediate/torch_compile_tutorial.html) version of our decoder model:
python

```
batch_size = 32
max_seqlen = 100

model = torch.compile(model)

benchmark(
    lambda: generate_sequence(
        model,
        max_seqlen=max_seqlen,
        batch_size=batch_size,
        use_cache=True,
        use_static_cache=True
    )
)
```

Model compilation results in an additional boost to runtime performance as shown in the table below:
![](../images/body-5473a44dd00f72e3df7d.webp)
Token Generation With torch.compile (By Author)
Note that we can apply model compilation when using dynamic caching, as well. However, torch.compile provides the best results when the computation graph is composed of fixed-sized tensors (e.g., see [here](https://towardsdatascience.com/maximizing-ai-ml-model-performance-with-pytorch-compilation/) for more details).
### The Performance Penalty of Early Stopping
An integral part of common token generators is checking for the end-of-sequence (EOS) at the end of each step. Without this test, token generators would always run for  _max_seqlen,_ even if all the sequences in the batch have ended. This could result in considerable computation waste and unnecessary latency — especially when common sequence lengths are much shorter than the maximum length. In the case of our toy experiment, we wait for all the sequences in the batch to end and discontinue token generation. Production-grade implementations will commonly perform  _continuous batching_ — replacing completed sequences with new prompts on the input queue.
python

```
        finished |= (new_tokens == config.eos_token_id)
        stop_gpu = torch.all(finished)
        
        # checking stop condition
        if stop_gpu.item():
            print(f"All sequences finished at step {i+1}")
            break
```

Importantly, the `.item()` call on the `stop_gpu` tensor, triggers a blocking host-device synchronization event. More specifically, in order to evaluate the conditional  _if_ statement, the CPU must wait for the GPU to complete its computation and copy the contents of the tensor to host memory. While the CPU waits, it is blocked from executing the next step of the token generation loop, or more accurately, it is blocked from loading the next computation kernels onto the GPU.
To measure the impact of the stopping condition on runtime performance, we add instrumentation for performance profiling with [NVIDIA Nsight™ Systems (nsys)](https://docs.nvidia.com/nsight-systems/index.html) using the [torch.cuda.profiler](https://github.com/pytorch/pytorch/blob/main/torch/cuda/profiler.py) and nvtx (v0.2.14) APIs. (See our [recent post](https://towardsdatascience.com/optimizing-data-transfer-in-ai-ml-workloads/) for more details on performance profiling with nsys).
python

```
ore details on performance profiling with nsys).

import nvtx
from torch.cuda import profiler

@torch.inference_mode()
def generate_sequence(
    model, max_seqlen, batch_size, use_cache=False, use_static_cache=False
):
    # Initialize prompts with BOS token
    all_tokens = torch.full(
        (batch_size, 1),
        config.bos_token_id,
        device=DEVICE,
        dtype=torch.long
    )
    finished = torch.zeros(batch_size, device=DEVICE, dtype=torch.bool)
    
    # Initialize static cache if requested
    if use_cache and use_static_cache:
        past_key_values = StaticCache(
            config=config,
            max_batch_size=batch_size,
            max_cache_len=max_seqlen,
            device=DEVICE,
            dtype=model.dtype
        )
    else:
        past_key_values = None
    
    # Initialize cache position tracking for static cache
    cache_positions = torch.arange(max_seqlen, device=DEVICE)
    
    for i in range(max_seqlen):
        if i == 30:
            # start nsys profiler
            torch.cuda.synchronize()
            profiler.start()
        elif i == 50:
            # stop nsys profiler
            torch.cuda.synchronize()
            profiler.stop()
        with nvtx.annotate(f"Step {i+1}", color="blue"):
            with nvtx.annotate("Model Forward", color="green"):
                current_input = (
                    all_tokens if past_key_values is None
                    else all_tokens[:, -1:]
                )
                cache_position = (
                    cache_positions[i:i+1] if use_static_cache else None
                )
                outputs = model(
                    current_input,
                    past_key_values=past_key_values,
                    cache_position=cache_position,
                    use_cache=use_cache
                )
                past_key_values = outputs.past_key_values
                logits = outputs.logits[:, -1, :]
                new_tokens = torch.argmax(logits, dim=-1)
                                all_tokens = torch.cat(
                    [all_tokens, new_tokens.unsqueeze(-1)],
                    dim=-1
                )
                finished |= (new_tokens == config.eos_token_id)
                stop_gpu = torch.all(finished)
            with nvtx.annotate("Check Stop Condition", color="red"):
                # checking stop condition
                if stop_gpu.item():
                    print(f"All sequences finished at step {i+1}")
                    break
    
    return all_tokens
```

We run our script using the  _cudaProfilerApi_ option to start and stop the profiler programmatically. Please see the official documentation for full details on [profiling from the nsys CLI](https://docs.nvidia.com/nsight-systems/UserGuide/index.html#profiling-from-the-cli).
bash

```
nsys profile \
  --capture-range=cudaProfilerApi \
  --trace=cuda,nvtx,osrt \
  --output=baseline \
  python train.py
```

The following trace, captured for a batch size of 16 and sequence length of 100, shows the GPU idling for about 110 microseconds in between steps — an eternity in the context of high-performance GPU workloads. This is a direct result of the synchronization event triggered by the EOS test.
![](../images/body-b18938655ed310285221.webp)
GPU Utilization Drops Between Each Step (By Author)
In production-grade implementations such synchronization issues are avoided by some combination of 1) use of lower level (e.g., C/C++) code that avoids the limitation of the Python interpreter, 2) using CUDA graphs to reduce overhead of kernel loading, 3) moving conditional checks onto the GPU using [conditional nodes](https://developer.nvidia.com/blog/dynamic-control-flow-in-cuda-graphs-with-conditional-nodes/), and 4) continuously and asynchronously preparing subsequent requests while the EOS check is in progress.
In the next section, we demonstrate a technique for hiding the overhead of the host-device synchronization in PyTorch using [CUDA streams](https://docs.pytorch.org/docs/stable/generated/torch.cuda.Stream_class.html).
## A CUDA Stream Optimization
A CUDA stream is a linear sequence of operations (kernels, memory copies, etc.) that execute in order on the GPU. While operations  _within_ a single stream are guaranteed to execute sequentially, operations in different streams can execute concurrently or overlap.
In previous posts (e.g., [here](https://towardsdatascience.com/pipelining-ai-ml-training-workloads-with-cuda-streams/) and [here](https://towardsdatascience.com/optimizing-data-transfer-in-ai-ml-workloads/)) we demonstrated the use of CUDA streams in pipelining common AI/ML workloads, e.g., executing a model on batch  _N_ while preparing batch  _N+1_. In this post we will use CUDA streams to enable the CPU to load the GPU kernels of step  _N+1_ before checking the stopping criteria of step  _N_. Contrary to our previous demonstrations of CUDA streams, our current example will not necessarily involve concurrent GPU kernel execution.  
We implement an alternative token generation function that interleaves two CUDA streams, running the following operations iteratively:
Program stream  _i%2_ to: **(A)** wait for stream (_i-1)%2_ to complete its generation of token  _i-1,__**(**_**B)** use the updated tensors to calculate the token  _i_ , **(C)** run the EOS test for token  _i_ on the GPU, and **(D)** perform a (non-blocking) copy of the EOS test result to pinned memory on the CPU.
On the default CUDA stream, wait for stream (_i-1)%2_ to complete its generation of token _i-1._
On the default CUDA stream, check if the stopping criteria for token  _i-1_ were met. If so, halt the generator and return. Otherwise, increment  _i_ and return to step 1.
Whereas previously, the initialization of token  _i_ generation was blocked by the EOS test on token  _i-1_ , the use of CUDA streams allows us to program the generation of token  _i_ **before** wecheck the result of the EOS test on token  _i-1_. In practice, the EOS test for token  _i-1_ on the CPU runs while the GPU is computing token  _i_.
python

```
@torch.inference_mode()
def generate_sequence_pipelined(
    model,
    max_seqlen,
    batch_size,
    use_cache=False,
    use_static_cache=False
):
    # Initialize prompts with BOS token
    all_tokens = torch.full(
        (batch_size, 1),
        config.bos_token_id,
        device=DEVICE,
        dtype=torch.long
    )
    finished = torch.zeros(batch_size, device=DEVICE, dtype=torch.bool)
    past_key_values = None
    
    # Initialize static cache if requested
    if use_cache and use_static_cache:
        past_key_values = StaticCache(
            config=config,
            max_batch_size=batch_size,
            max_cache_len=max_seqlen,
            device=DEVICE,
            dtype=model.dtype
        )
    
    # Initialize cache position tracking for static cache
    cache_positions = torch.arange(max_seqlen, device=DEVICE)
    
    # Dual streams for pipelining
    streams = [torch.cuda.Stream(), torch.cuda.Stream()]
    stop_host = [
        torch.tensor(False, pin_memory=True),
        torch.tensor(False, pin_memory=True)
    ]
    
    for i in range(max_seqlen):
        curr_idx, prev_idx = i % 2, (i+1) % 2
        curr_s, prev_s = streams[curr_idx], streams[prev_idx]
        
        # Launch iteration i in current stream
        with torch.cuda.stream(curr_s):
            # program stream to wait for previous stream to complete
            curr_s.wait_stream(prev_s)
            current_input = (
                all_tokens if past_key_values is None
                else all_tokens[:, -1:]
            )
            cache_position = (
                cache_positions[i:i+1] if use_static_cache else None
            )
            outputs = model(
                current_input,
                past_key_values=past_key_values,
                cache_position=cache_position,
                use_cache=use_cache
            )
            past_key_values = outputs.past_key_values
            logits = outputs.logits[:, -1, :]
            new_tokens = torch.argmax(logits, dim=-1)
            all_tokens = torch.cat(
                [all_tokens, new_tokens.unsqueeze(-1)],
                dim=-1
            )
            
            finished |= (new_tokens == config.eos_token_id)
            stop_gpu = torch.all(finished)
            stop_host[curr_idx].copy_(stop_gpu, non_blocking=True)
        
        # Check previous iteration's stop signal
        torch.cuda.current_stream().wait_stream(prev_s)
        if stop_host[prev_idx].item():
            print(f"All sequences finished at step {i}")
            break
    
    return all_tokens
```

The image below captures the nsys trace for our new token generator:
![](../images/body-fed6bef0164ba922642a.webp)
Constant GPU Activity When Applying CUDA Streams (By Author)
In the CUDA section of the trace we can see the use of two CUDA streams, with token generation being passed back and forth in a sort of ping-pong effect: One stream generates all of the odd tokens and second all of the even tokens. The CPU is about half a step ahead of the GPU — allowing it to program step  _i_ while the GPU is computing step  _i-1_. The CPU-side EOS stop-check of step  _i-1_(in red) occurs after step  _i_ is fully programmed (and has started running).Most importantly, we now find the GPU utilization to be consistent — the idling we saw before is gone.
The CUDA stream interleaving results in an additional performance boost, as shown in the table below:
![](../images/body-9dd2d338c040c1f98e47.webp)
Token Generation With CUDA Streams (By Author)
We would expect the benefit of the ping-pong solution we have implemented to be impacted by the ratio between the GPU idle time (i.e., the overhead of kernel loading) and the kernel computation time. To test this, we fix the sequence length at 100 and rerun the benchmark for a number of batch sizes:
![](../images/body-ad870a33113327e829b9.webp)
Impact of Pipelining for Varying Batch Size (By Author)
As expected, the highest performance gain, 11.6%, occurs when the batch size is smallest and the kernel computation load is at its lowest. As the kernel compute increases, the ratio of kernel loading to kernel compute time decreases as does the impact of CUDA stream interleaving.
Note that there is some overhead to the use of CUDA streams. This can be demonstrated by comparing our interleaving solution to a token generator that skips the EOS test altogether:
![](../images/body-ddaf101a5dba6cd682d0.webp)
Overhead of CUDA Stream Interleaving (By Author)
### The Potential Performance Pitfalls of Using CUDA Streams
CUDA streams should be used with extreme caution. When using the [default stream](https://docs.pytorch.org/docs/stable/generated/torch.cuda.default_stream.html) we can rely on PyTorch to perform any necessary synchronization when data is moved around. However, when using CUDA streams, we must ensure appropriate synchronization explicitly. In particular, we must ensure appropriate data transfer between the streams. Otherwise, we may experience CUDA errors (e.g., “device-side assert triggered”) — if we are lucky. If we are less lucky, we may experience data corruption without even knowing it. See the PyTorch [CUDA stream](https://docs.pytorch.org/docs/stable/notes/cuda.html#cuda-streams) documentation for more details on appropriate use.
For AI/ML workloads with large CUDA memory utilization, such as LLMs, another consideration is memory utilization. The PyTorch caching allocator manages memory on a per-stream basis; using multiple streams can lead to increased memory reservation and fragmentation. These could result in increased memory faults that might overshadow the potential gains from the use of streams.
## Results
In the table below we summarize the runtime results of applying static caching, compilation, and pipelining on a batch of 32 sequences and a maximum sequence length of 100. The results are sorted in increasing order of performance:
![](../images/body-656754222c236ea5bc2b.webp)
Token Generation Optimization Results (By Author)
In the case of our toy GPT-2 model, the best results — nearly 5 times the baseline performance — are achieved when employing PyTorch compilation and the CUDA stream interleaving method discussed in this post. However, as we have seen, the impact of CUDA interleaving could vary greatly based on the properties of the workload and runtime environment, particularly on the ratio between the kernel loading time and the kernel compute time. Please be sure to run your own benchmarks before adopting this method.
## Summary
In high-performance AI engineering, any hint of GPU under-utilization presents an opportunity for optimization. One of the primary optimization tools on NVIDIA GPUs is CUDA streams. In this post, we demonstrated their use in solving the idle GPU time that results from the host-device synchronization associated with early-stopping in PyTorch-native autoregressive token generation. By interleaving CUDA streams in a “ping-pong” pattern, we successfully hid the latency imposed by the EOS-check which resulted in a meaningful increase the workload’s throughput. By combining this technique with the well-known methods of model compilation and static caching, we can maximize the performance of PyTorch-native inference.
## Register Now to Continue Reading
### Unlock Towards Data Science with a Free Account
All articles on Towards Data Science are available to registered users. Create a free account today to access this article and thousands more.
Register for freeSign in to your account
* * *
![](../images/body-97f5483d57a9b8741146.jpeg)
Written By
## [Chaim Rand](https://towardsdatascience.com/author/chaimrand/)
[See all from Chaim Rand](https://towardsdatascience.com/author/chaimrand/)
[Deep Dives](https://towardsdatascience.com/tag/deep-dives/)[Cuda](https://towardsdatascience.com/tag/cuda/)[Deep Learning](https://towardsdatascience.com/tag/deep-learning/)[Optimization](https://towardsdatascience.com/tag/optimization/)[Pytorch](https://towardsdatascience.com/tag/pytorch/)
Share This Article
  * [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Ftowardsdatascience.com%2Foptimizing-token-generation-in-pytorch-decoder-models%2F&title=Optimizing%20Token%20Generation%20in%20PyTorch%20Decoder%20Models)
  * [](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Ftowardsdatascience.com%2Foptimizing-token-generation-in-pytorch-decoder-models%2F&title=Optimizing%20Token%20Generation%20in%20PyTorch%20Decoder%20Models)
  * [](https://x.com/share?url=https%3A%2F%2Ftowardsdatascience.com%2Foptimizing-token-generation-in-pytorch-decoder-models%2F&text=Optimizing%20Token%20Generation%20in%20PyTorch%20Decoder%20Models)


Towards Data Science is a community publication. Submit your insights to reach our global audience and earn through the TDS Author Payment Program.
[Write for TDS](https://towardsdatascience.com/submissions/)
## Related Articles
  * [![](../images/002.jpeg)](https://towardsdatascience.com/my-story-of-taking-the-tensorflow-developer-certification-exam-85d4fb777cbb/)
## [My Story of Taking the TensorFlow Developer Certification Exam](https://towardsdatascience.com/my-story-of-taking-the-tensorflow-developer-certification-exam-85d4fb777cbb/)
[Deep Learning](https://towardsdatascience.com/category/artificial-intelligence/deep-learning/)
My overall experience of taking the exam, how I prepared for it, and what I would've done differently if I had to take the exam again.
[Ruben Winastwan](https://towardsdatascience.com/author/marcellusruben/)
July 17, 20209 min read
  * [![](../images/003.gif)](https://towardsdatascience.com/towards-infinite-llm-context-windows-e099225abaaf/)
## [Towards infinite LLM context windows](https://towardsdatascience.com/towards-infinite-llm-context-windows-e099225abaaf/)
[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)
From 512 to 1M+ tokens in 5 years - LLMs have rapidly expanded their context windows. Where's the limit?
[Krzysztof K. Zdeb](https://towardsdatascience.com/author/krzysztof.kornel/)
April 28, 202410 min read
  * [![](../images/004.png)](https://towardsdatascience.com/resnets-residual-blocks-deep-residual-learning-a231a0ee73d2/)
## [ResNets - Residual Blocks & Deep Residual Learning](https://towardsdatascience.com/resnets-residual-blocks-deep-residual-learning-a231a0ee73d2/)
[Deep Learning](https://towardsdatascience.com/category/artificial-intelligence/deep-learning/)
Deep Learning harnesses the power of Big Data by building deep neural architectures that try to approximate a function f(x) that can map...
[Prem Oommen](https://towardsdatascience.com/author/thedataoracle/)
November 28, 20204 min read
  * [![](../images/005.png)](https://towardsdatascience.com/10-examples-of-awful-data-that-i-had-to-work-with-as-a-data-scientist-10894cd35828/)
## [10 Examples of Awful Data that I had to work with as a Data Scientist](https://towardsdatascience.com/10-examples-of-awful-data-that-i-had-to-work-with-as-a-data-scientist-10894cd35828/)
[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)
A glimpse into the frustrating dark side of working with data
[Terence Shin, MSc, MBA](https://towardsdatascience.com/author/terenceshin/)
November 11, 20206 min read
  * [![](../images/006.jpeg)](https://towardsdatascience.com/outlier-aware-clustering-beyond-k-means-76f7bf8b4899/)
## [Outlier-Aware Clustering: Beyond K-Means](https://towardsdatascience.com/outlier-aware-clustering-beyond-k-means-76f7bf8b4899/)
[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)
Can data science help you choose your next job? First of all, that's a terrible idea.
[Mathieu Lemay](https://towardsdatascience.com/author/lsci/)
November 29, 20182 min read
  * [![](../images/007.jpg)](https://towardsdatascience.com/will-the-covid-19-vaccine-alter-my-dna-and-hook-me-to-an-ai-f7d086b94e07/)
## [Will the COVID-19 vaccine alter my DNA and hook me to an AI?](https://towardsdatascience.com/will-the-covid-19-vaccine-alter-my-dna-and-hook-me-to-an-ai-f7d086b94e07/)
[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)
Checking your facts in the time of the pandemic
[Alexiei Dingli](https://towardsdatascience.com/author/alexieidingli/)
August 15, 20205 min read
  * [![](../images/008.jpeg)](https://towardsdatascience.com/how-to-calculate-the-mean-and-standard-deviation-normalizing-datasets-in-pytorch-704bd7d05f4c/)
## [How to calculate the mean and standard deviation - Normalizing datasets in Pytorch](https://towardsdatascience.com/how-to-calculate-the-mean-and-standard-deviation-normalizing-datasets-in-pytorch-704bd7d05f4c/)
[Deep Learning](https://towardsdatascience.com/category/artificial-intelligence/deep-learning/)
Neural networks converge much faster if the input data is normalized. Learn the reason why and how to implement this.
[Jorrit Willaert](https://towardsdatascience.com/author/jorritwillaert98/)
September 24, 20213 min read
  * [![](../images/009.jpeg)](https://towardsdatascience.com/10-examples-to-master-python-dictionary-comprehensions-7aaa536f5960/)
## [10 Examples to Master Python Dictionary Comprehensions](https://towardsdatascience.com/10-examples-to-master-python-dictionary-comprehensions-7aaa536f5960/)
[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)
A comprehensive practical guide
[Soner Yıldırım](https://towardsdatascience.com/author/sonery/)
January 2, 20215 min read
  * [![](../images/010.png)](https://towardsdatascience.com/an-existential-crisis-of-a-veteran-researcher-in-the-age-of-generative-ai/)
## [An Existential Crisis of a Veteran Researcher in the Age of Generative AI](https://towardsdatascience.com/an-existential-crisis-of-a-veteran-researcher-in-the-age-of-generative-ai/)
[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)
If you have had an existential moment in your career due to AI, let me know.
[Pedram Ataee](https://towardsdatascience.com/author/pedram-ataee/)
April 23, 20255 min read


  * [YouTube](https://www.youtube.com/c/TowardsDataScience)
  * [X](https://x.com/TDataScience)
  * [LinkedIn](https://www.linkedin.com/company/towards-data-science/)
  * [Threads](https://www.threads.net/@towardsdatascience)
  * [Bluesky](https://bsky.app/profile/towardsdatascience.com)


[](https://towardsdatascience.com/)
Your home for data science and AI. The world's leading publication for data science, data analytics, data engineering, machine learning, and artificial intelligence professionals.
Subscribe to Our Newsletter[Write For TDS](https://towardsdatascience.com/submissions/)•[About](https://towardsdatascience.com/about-towards-data-science-d691af11cc2f/)•[Advertise](https://contact.towardsdatascience.com/advertise-with-towards-data-science)•[Privacy Policy](https://towardsdatascience.com/privacy-policy/)•[Terms of Use](https://towardsdatascience.com/website-terms-of-use/)
© Insight Media Group, LLC 2026•[The New Stack](https://thenewstack.io)•[roadmap.sh](https://roadmap.sh)•[Creator Network](https://insightmediagroup.io/creator-network)
####  Recommended Articles 
Close
  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607639/image?etag=1781250125848&width=500&height=500) Javascript Fatigue: HTMX Is All You Need to Build ChatGPT — Part 1 ](https://towardsdatascience.com/javascript-fatigue-you-dont-need-js-to-build-chatgpt/ "Javascript Fatigue: HTMX Is All You Need to Build ChatGPT — Part 1")
  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607930/image?etag=1782124566054&width=500&height=500) The Subset Sum Problem Solved in Linear Time for Dense Enough Inputs ](https://towardsdatascience.com/subset-sum-problem-solved-in-linear-time-for-dense-enough-inputs/ "The Subset Sum Problem Solved in Linear Time for Dense Enough Inputs")
  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/3660/image?etag=1780176246070&width=500&height=500) Does Your Company Have a Data Strategy? ](https://towardsdatascience.com/does-your-company-have-a-data-strategy-e8944dbec4ae/ "Does Your Company Have a Data Strategy?")
  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/4970/image?etag=1780565225463&width=500&height=500) Productivity Tips, Data Career Insights, and Other Recent Must-Reads ](https://towardsdatascience.com/productivity-tips-data-career-insights-and-other-recent-must-reads-5c47eb7a2fe0/ "Productivity Tips, Data Career Insights, and Other Recent Must-Reads")


