• Updated:
  • Featured post

How LLMs get their probabilities for the next token

(este post es una explicación de la teoría. dejo aquí otro post con el detalle práctico de como usar la temperature, top_k y top_p a efectos prácticos)

Explicación más en detalle de cómo obtienen los LLMs las probabilidades para generar el siguiente token

Sampling

En cada posición el modelo tiene una bolsa con miles de tokens posibles. Sampling es el proceso de no coger siempre el token más probable, si no de orientarlo a coger respuestas con determinadas características.

Si el modelo escogiera siempre el token con más probabilidades, obtendríamos respuestas aburridas y repetitivas.

Logits

Para generar el siguiente token, una red neuronal calcula primero los vectores de logits, donde cada logit corresponde a un valor posible. El tamaño de estos vectores de logits es tan grande como el vocabulario completo del modelo.

(representación de vectores de logits)

flowchart LR
	N1["What's your favorite color?"]:::note --> Z
	Z --> A1 --> A
	Z --> B1 --> B
	Z --> C1 --> C
	Z --> D1 --> D
	
	Z["Neural network"]
    A["a"]
    A1["(-0.5)"]
    B["green"]
    B1["(0.7)"]
    C["red"]
    C1["(0.5)"]
    D["the"]
    D1["(-1.2)"]
    
    classDef note fill:none,stroke:none,color:#777;    

Los logits NO representan probabilidades ya que no suman 1 y pueden incluso ser negativos (la probabilidades no pueden). Para convertir logits a probabilidades se usa una Softmax layer

Temperature

La temperatura es una constante que se aplica a los logits antes de la transformación de la Softmax layer. Se usa para ajustar la creatividad del modelo y redistribuir la probabilidad de los valores. Una temperatura más alta hace que el modelo sea más creativo ya que aumenta las posibilidades de elegir tokens menos probables.

xychart-beta
  title "Temperatura vs Probabilidad"
  x-axis "Temperatura (T)" [0.1, 0.2, 0.5, 1, 2, 5]
  y-axis "Probabilidad" 0 --> 1
  line "P(token1)" [0.9999546, 0.9933071, 0.8807971, 0.7310586, 0.6224593, 0.5498340]
  line "P(token2)" [0.0000454, 0.0066929, 0.1192029, 0.2689414, 0.3775407, 0.4501660]

Ejemplos de temperaturas:

  • Low (0.2-0.3): El modelo es cauto y elige las palabras más probables. Output factual y predecible.
  • Medium (0.5-0.7): Un mix de confiabilidad y engagement
  • High (0.9-1.0): Toma riesgos y es impredecible

Read More

Concentration Methods

Pomodoro

25 min trabajo + 5 min descanso; a la tercera vez - descanso 15 mins Ideal para tareas cortas y variadas. Ritmo constante, evita fatiga.

52/17 rule

52 mins de trabajo + 17 mins descanso. Ideal para tareas de media duración. Equilibrio entre productividad y descanso pero menos flexible que pomodoro.

Ollama & OpenWeb UI (local LLMs)

Ollama’s github repository (to check for updates)
Ollama’s web (to check for models)
OpenWeb UI (to check for docker commands)

Install locally

Prerequisites

I’m running OpenWeb UI through docker.

First of all check you have docker.desktop open. It may tell you to update WSL. Afterwards please check your docker is able to run containers

docker run hello-world

Ollama

ollama ls # see local models
ollama run gpt-oss # run model
ollama rm gemma3 # delete model

inside a model

/? # see help

# this creates a 'blueprint' you can save and load multiple times to give the LLM some context
/save <model>
/load <model>

/clear
/bye (or ctrl+D)

Read More

Screaming architecure

Es un principio de organización de proyectos. Percibe como estructurar el código en un proyecto.

Concepto propuesto por Robert C. Martin donde la arquitectura deberia gritar el dominio de negocio por módulos de dominio y no los detalles técnicos por capas técnicas.

propuesta habitual

Controllers/
Repositories/
Data/
Services/

screaming architecture

Invoices/
  CreateInvoice/
  PayInvoice/
  CancelInvoice/
Customers/
  RegisterCustomer/
  UpdateCustomer/

La desventaja es que puede incurrir en duplicación de código y requiere de conocimiento técnico avanzado.

C# User Secrets

Never store passwords or sensitive data in source code or configuration files. Production secrets shouldn’t be used for development or test. Secrets shouldn’t be deployed with the app. Production secrets should be accessed through a controlled means like Azure Key Vault.

Secret manager

This tool hides implementation details. The secret values are stored in a JSON file in the local machine’s user profile folder.

This tool operates on project-specific configuration settings and (!) it’s only meant for local development (!). Don’t use it for production as it’s not encrypted.

To use user secrets, run the following command in the project directory

dotnet user-secrets init

You can do this through visual studio Right click on your project inside vstudio > Administrar secretos de usuario

Set a new secret

Define an app secret containing a key > value

dotnet user-secrets set "OpenAI:ApiKey" "sk-xxxx"

Read More

.NET AI integration

Today’s AI landscape moves so fast and providers differ so much that vendor lock-in can become expensive. You need a clean, testeable way to add AI without tying your architecture to one SDK.

The solution to this problem is a model-agnostic solution.

Nuggets to use (you need to click “see preliminar versions”):

  • Microsoft.Extensions.AI - This nugget implements IChatClient interface, which is an abstraction to use several LLM providers, from ChatGPT to Ollama.
  • Microsoft.Extensions.AI.OpenAI
  • OllamaSharp (previously Microsoft.Extensions.AI.Ollama)

You’ll need to go to Open AI platform to set up a project, billing, and get an openAI API key.

This repository is a test implementation which connects to OpenAi’s ChatGPT and is able to send prompts.

Best Practices

  • Keep inputs short and specific
  • Validate outputs with regex/JSON schema. Reject or re-ask when invalid
  • Log prompts, token counts, latency and provider responses
  • Improve cost ops. Cache results, batch requests and prefer smaller models by default
  • Don’t commit or send secrets or personal information
  • Failover. Implement timeouts, retries, and fallback models
  • LLMs are stateless; maintaining and reconstructing conversational context is a developer’s responsibility (chat history or memory abstractions)

Security

  • prompt injection: beware with malicious prompts to subvert model guardrails, steal data or execute unintended actions
  • LLMs may leak private or internal data via crafted prompts
  • Training data poisoning may be injected by malicious actors
  • DoS and rate limiting: prevent overuse / abuse

Reference(s)

https://roxeem.com/2025/09/04/the-practical-net-guide-to-ai-llm-introduction/
https://roxeem.com/2025/09/08/how-to-correctly-build-ai-features-in-dotnet/

EF Core multithreading

I’ve had issues with EF Core when operating with multiple threads and with multiple calls at the same time.

The most important things to check are:

  1. The DbContext is not being shared between calls or threads
  2. All classes which have the context inyected must be scoped (not singleton)
  3. If working with async methods, you need to await calls

I have the following service

public class PersonService(AppDbContext _context)
{
	public async Task<Person> GetPerson(string id)
	{
		return await context.Persons.Find(id);
	}
}

which I may configure as follows

// if I inject it as singleton, this would cause exceptions on multiple calls
services.AddSingleton<IPersonService, PersonService>

// we have to inject it as scoped so it creates a context new for each call
services.AddScoped<IPersonService, PersonService>

Caching in .NET (IMemoryCache)

.NET offers several cache types. I’m going to explore here IMemoryCache which stores data in the memory of the web server. It’s simple but not suitable for distributed scenarios.

first of all we need to register the services

builder.Services.AddMemoryCache();

GetOrCreateAsync

here’s how you can inject and use it, without manipulating the cache itself

public class PersonService(IMemoryCache _cache)
{
	private const string CACHE_PERSON_KEY = "PersonService:GetPerson:";

	public async Task<Person> GetPerson(string id)
	{
		return await _cache.GetOrCreateAsync(CACHE_PERSON_KEY + id, async entry =>
		{
			entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
			return await GetPersonNoCache(id);
		});
	}

	public async Task<Person> GetPersonNoCache(string id)
	{
		// do operations to get a person here
	}
}

Read More

Three-point estimation

Split tasks in its minimum definition and estimate those minimum tasks by Optimistic (O) - Most Likely (M) - Pessimistic (P).

With those estimations we do PERT distribution and then add those estimations

(O+(4xM)+P)/6

Example

Task: Migrate x database Minimum tasks:

  • migrate service 1 to y database
  • migrate service 2 to y database
  • migrate connector to use y database
  • test changes in test env

Then we estimate those tasks

task Optimistic Most likely Pessimistic PERT Comments
migrate service 1 10h 25h 55h 28h (I round hours up)
migrate service 2 4h 14h 22h 14h take x into account
migrate connector 20h 40h 80h 44h  
test changes 2h 7h 14h 8h  
total estimation for task       94h  

Reference(s)

https://www.knowledgehut.com/blog/project-management/three-point-estimating

C# Async await with parallelism

The following is an example where we need to call and await an external API multiple times inside an iteration.

I’m using myFakeAPI from postman for this example and one of their Car response look like this

public class CarResponse
{
	public CarDto Car { get; set; }
}

public class CarDto
{
	public int Id { get; set; }
	public string Car { get; set; }
	public string Car_Model { get; set; }
	public string Car_Color { get; set; }
	public int Car_Model_Year { get; set; }
	public string Car_Vin { get; set; }
	public string Price { get; set; }
	public bool Availability { get; set; }
}

Then this is the method which does call and mapping

private async Task<CarResponse> ExecuteCall(string id)
{
	string combinedUrl = URL + id;

	using var response = await _httpClient.GetAsync(combinedUrl);
	response.EnsureSuccessStatusCode();

	string json = await response.Content.ReadAsStringAsync();
	return JsonConvert.DeserializeObject<CarResponse>(json);
}

Control

This is the control version where we launch and await the tasks one at a time

// DON'T DO THIS
private async Task<List<CarResponse>> Control()
{
	List<CarResponse> carList = [];
	foreach (string id in _idsList)
	{
		CarResponse singleCar = await ExecuteCall(id);
		carList.Add(singleCar);
	}
	return carList;
}

Task.WhenAll()

It’s the most simple one - all tasks are launched at the same time. It’s ideal when we don’t have limits as we have no control over the simultaneous number of calls

// simple but what if we'd have +100 calls?
private async Task<List<CarResponse>> TaskWhenAll()
{
	var getCarsTask = _idsList.Select(ExecuteCall);
	var cars = await Task.WhenAll(getCarsTask);
	return cars.ToList();
}

Parallel.ForEachAsync()

This gives us the most control over number of parallel calls. It’s more complex.

private async Task<List<CarResponse>> ParallelForEachAsync()
{
	// this is a secure collection for multiple threads
	var carsBag = new ConcurrentBag<CarResponse>();
	var options = new ParallelOptions { MaxDegreeOfParallelism = 5 };

	await Parallel.ForEachAsync(_idsList, options, async (id, ct) =>
	{
		CarResponse car = await ExecuteCall(id);
		carsBag.Add(car);
	});
	return carsBag.ToList();
}