Modelos de inteligencia artificial en local.

13 poruka, 1 stranica:  1 ↖ Vrati se nazad na listu tema

Skor: +7

1. Rayo,

como correr modelos de inteligencia artificial de forma local

Bienvenides todes a este su tutorial de ollama y modelos de IA en local.
esta vez no quiero usar IIA para escribir, entonces tendrán esa sensación humana, aunque comienzo a dudarlo porque ciertos
individuos ya me han recalcado que tengo la estructura gramatical de una, aunque, lastimosamente para mi, no es así, aún.
Pero vamos al lío.

Ollama

¿Qué es ollama? la amigable vaquita es un programa software libre que nos permite ejecutar llm (long language model) o algunos otros tipos de redes neuronales de forma local. Es decir, sin enviar datos a servidores externos y sin necesidad de internet.

¿Qué necesitamos?

Iremos desde el requisito más indispensable, al menos. Los opcionales o los que tengan una nota, estarán marcados con un (?).

  • Tener instalado ollama para su sistema operativo, se descarga desde. https://ollama.com/download
  • Una targeta gráfica dedicada (?): Es lo más recomendable para el funcionamiento de los modelos.
  • 12 gb de ram: Esto es casi indispensable y aún más si no se cuenta con una tarjeta gráfica dedicada.
  • una CPU de mínimo 4 núcleos: Lo mismo, se recomienda que sean mínimo 8 hilos porque en caso de no tener gráfica dedicada tirará de la ram y de la CPU para hacer inferencia.

¿cómo se hace?

Ya que tenemos ollama descargado, iniciamos su instalación.
no voy a explicar como se instala, si quieres usar modelos en local asumiremos que conoces lo básico de tu sistema operativo y lo más indispensable de la línea de comandos.
luego de que termine de instalarse ollama se desaparecerá la ventana y tendremos el ícono de ollama en la bandeja del sistema.
Ahora sí

¿Cómo descargar modelos?

en la web de ollama tentendremos los modelos open source disponibles.
antes de iniciar a descargar, vamos a hablar de modelos.
¿Qué son esas cosas de 1b, 7b y todo eso?
Son los parámetros de entrenamiento. la b al lado del número quiere decir billones de parámetros. 1b es un billón 7b son 7 billones y así susesibamente.
esto quiere decir que a más número de parámetros más potencia de cómputo requiere.
Vamos a hacer la instalación de un modelo chiquito de google, gema.

  1. vamos al cuadro de búsqueda en ollama.com
  2. buscamos gema
  3. clicleamos en el primer resultado de encabezado nivel 2 con el nombre del modelo a descargar.
  4. con la b buscamos el botón que nos dejará elejir la versión de parámetros, en este caso 7b.
  5. Daremos enter, y escape. luego buscaremos el que nuestra máquina soporte, en mi caso, el de 2b y pulsaremos enter.
  6. con la e buscaremos el cuadro de texto que nos dará la instrucción de ollama, que será algo así:
  7. ollama run >nombre del modelo:numero de paŕ́ametros b>
    Debajo tendremos un botón sin etiqueta y daremos enter en el para copiarlo al portapapeles.
  8. abriremos un cmd o powershell
  9. pegaremos el comando, en mi caso: ollama run gemma:2b y daremos enter

Nota: si les llega a dar error, recarguen el servidor de ollama con el comando:
ollama server
Si no funciona, abran el menú inicio, busquen ollama y pulsen enter; luego cierren y abran el terminal y corran el comando de nuevo.
Debería a iniciar la descarga.

Activando el modelo

Luego de que termine la descarga pueden pasar 2 cosas.

  1. que entren a la terminal del modelo, que se pueden dar cuenta por los > que les aparezcan.
  2. que no entren y vuelvan a la terminal normal de su sistema operativo.
  3. Para el segundo caso, prueben correr el comando ollama run de nuevo y si bien le les va, abrirá la terminal.
    Si no... el modelo iniciará la descarga otra vez.
    Esto no se por que ocurra, pero ocurre. me pasó ya 2 veces y es mejor dejar nota.

interactuando con el modelo

Si ya estamos en la terminal, no hace falta más que empezar a interactuar con el modelo.
>>> hola! quién eres?
Soy un modelo artificial, creado por Google. Soy un chat bot que puede ayudarte con una variedad de tareas, como
responder preguntas, generar contenido, y proporcionar información.

>>> Send a message (/? for help)
¡Listo! tendremos un modelo de inteligencia artificial corriendo en nuestro sistema.

comandos útiles

  • Borar un modelo: ollama rm <nombre del modelo>
  • Listar los modelos descargados: ollama list
  • correr un modelo: ollama run <nombre del modelo>
  • Cerrar el chat con el modelo: ctr + d
  • Cortar el flujo de respuesta: ctrl + c
  • borrar los datos y la conversación hasta ese momento. /clear
  • Setear variables o información: /set <parámetro>
  • ver la ayuda de ollama: /help
notas importantes.
  1. Si no tienes más de 8gb de ram sin tarjeta dedicada, no pierdas el tiempo, no te funcionará.
  2. Si tiras de pura ram para ejecutar los modelos, se paciente, tardarán algo en responder, sobre todo los de grandes mayores a 7b.
  3. No esperes una experiencia fluída si no tienes los recursos de cómputo necesarios.
  4. No esperes que sean de la misma calidad de los modelos comerciales, ni que estén adaptados a un chatbot común y corriente. Tendrás que familiarizarte con prompt ingeniéerin mucho más profundo porque su comprendimiento de lenguaje natural no está tan pulido, esto para los modelos chiquitos como llama 1b, r1 1b, etc.
  5. dame más uno, no seas hijo de puta.

Skor: +13

Poslednja izmena od strane Rayo, nedelja 04:16

2. Yostein-san,

Francamente, buen aporte. Esperamos que tal vez, ese hulo proporcione más recursos nuevos de todos para facilitar con más cosas.

Skor: +1

3. el-prevoste,

punctualización que puede serles útil. cuando les responde, no les va a responder en un texto entero, irá por fragmentos casi inentendibles. si quieren leer todo de golpe, esperen a que termine y lean con la tecla 7 y 9 del cursor de nvda

Skor: +0

4. itachi,

Buenas.
Alguno llegó a probar el modelo vision? Qué tan preciso llega a ser en comparación con Be My AI?

Skor: +0

5. Rayo,

les dejo un script chiquito que hice para interactuar con los modelos.
es fácil de usar, al correrlo les pide elegir el modelo a usar, y la interfaz es intuitiva. alt c para ir al cuadro de texto, alt e para enviar, alt l para ir a la lista de mensajes, alt p para cambiar el prompt de sistema, alt s para cerrar la aplicación.

import wx
import requests
import json
import threading
import subprocess
import sys
import re
import time

def obtener_modelos_disponibles():
"""
Ejecuta 'ollama list' para obtener la lista de modelos disponibles y retorna
una lista con los nombres de los modelos (columna NAME).
"""
try:
result = subprocess.run(["ollama", "list"], capture_output=True, text=True, shell=True)
if result.returncode != 0:
return []

lines = result.stdout.splitlines()
models = []
for line in lines:
line = line.strip()
if not line:
continue
# Ignoramos la cabecera
if "NAME" in line and "ID" in line and "SIZE" in line:
continue

parts = line.split()
if parts:
models.append(parts[0])
return models

except Exception:
return []

def eliminar_texto_entre_think(texto):
"""
Elimina todo lo que esté entre <think>...</think>, incluyendo dichas etiquetas.
"""
return re.sub(r"<think>.*?</think>", "", texto, flags=re.DOTALL)

def extraer_nombre_modelo(modelo_str):
"""
Retorna la parte previa a ':' si existe,
por ejemplo 'gemma:2b' -> 'gemma'.
Si no hay ':', retorna el nombre completo.
"""
return modelo_str.split(':')[0]

class ModeloSelectionFrame(wx.Frame):
"""
Ventana de selección de modelos.
Muestra un ComboBox con los modelos disponibles y los botones Aceptar y Cancelar.
"""

def __init__(self, parent=None, title="Seleccionar Modelo"):
super().__init__(parent, title=title, size=(500, 200))

panel = wx.Panel(self)

label_modelos = wx.StaticText(panel, label="&Elige el modelo con el que quieres chatear:")
self.combobox_modelos = wx.ComboBox(panel, style=wx.CB_READONLY)

self.btn_aceptar = wx.Button(panel, id=wx.ID_OK, label="&Aceptar")
self.btn_cancelar = wx.Button(panel, id=wx.ID_CANCEL, label="&Cancelar")

modelos = obtener_modelos_disponibles()
self.combobox_modelos.AppendItems(modelos)
if modelos:
self.combobox_modelos.SetSelection(0) # Seleccionar el primero por defecto

# Eventos
self.btn_aceptar.Bind(wx.EVT_BUTTON, self.on_aceptar)
self.btn_cancelar.Bind(wx.EVT_BUTTON, self.on_cancelar)

# Sizers
sizer_principal = wx.BoxSizer(wx.VERTICAL)
sizer_combo = wx.BoxSizer(wx.HORIZONTAL)
sizer_botones = wx.BoxSizer(wx.HORIZONTAL)

sizer_combo.Add(label_modelos, 0, wx.ALL wx.ALIGN_CENTER_VERTICAL, 5)
sizer_combo.Add(self.combobox_modelos, 1, wx.ALL wx.EXPAND, 5)

sizer_botones.Add(self.btn_aceptar, 0, wx.ALL, 5)
sizer_botones.Add(self.btn_cancelar, 0, wx.ALL, 5)

sizer_principal.Add(sizer_combo, 1, wx.EXPAND wx.ALL, 10)
sizer_principal.Add(sizer_botones, 0, wx.ALIGN_CENTER wx.ALL, 5)

panel.SetSizer(sizer_principal)

self.Centre()
self.Show()

def on_aceptar(self, event):
modelo_seleccionado = self.combobox_modelos.GetValue()
if not modelo_seleccionado:
wx.MessageBox("Por favor, selecciona un modelo.", "Atención", wx.OK | wx.ICON_WARNING)
return

chat_frame = ChatFrame(modelo=modelo_seleccionado)
chat_frame.Show()
self.Close()

def on_cancelar(self, event):
self.Close()
wx.GetApp().ExitMainLoop()

class SystemPromptDialog(wx.Dialog):
"""
Diálogo para editar o añadir el Prompt de Sistema.
"""

def __init__(self, parent, prompt_actual=""):
super().__init__(parent, title="Editar Prompt de Sistema", size=(500, 300))

panel = wx.Panel(self)
etiqueta = wx.StaticText(panel, label="Escribe el Prompt de Sistema aquí:")
self.text_prompt = wx.TextCtrl(panel, style=wx.TE_MULTILINE, size=(450, 150))
self.text_prompt.SetValue(prompt_actual)

btn_aceptar = wx.Button(panel, wx.ID_OK, "&Aceptar")
btn_cancelar = wx.Button(panel, wx.ID_CANCEL, "&Cancelar")

sizer_principal = wx.BoxSizer(wx.VERTICAL)
sizer_botones = wx.BoxSizer(wx.HORIZONTAL)

sizer_principal.Add(etiqueta, 0, wx.ALL, 5)
sizer_principal.Add(self.text_prompt, 1, wx.ALL | wx.EXPAND, 5)

sizer_botones.Add(btn_aceptar, 0, wx.ALL, 5)
sizer_botones.Add(btn_cancelar, 0, wx.ALL, 5)

sizer_principal.Add(sizer_botones, 0, wx.ALIGN_CENTER)

panel.SetSizer(sizer_principal)
self.Centre()

def get_prompt(self):
return self.text_prompt.GetValue().strip()

class MessageDetailFrame(wx.Frame):
"""
Ventana de detalle de un mensaje. Muestra en modo de solo lectura
el contenido completo del mensaje seleccionado.
"""

def __init__(self, parent, title, content):
super().__init__(parent, title=title, size=(500, 400))

panel = wx.Panel(self)
txt = wx.TextCtrl(panel, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL, size=(480, 360))
txt.SetValue(content)

sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txt, 1, wx.ALL | wx.EXPAND, 5)
panel.SetSizer(sizer)

self.Centre()
self.Show()

class ChatFrame(wx.Frame):
"""
Ventana principal del chat con Ollama.
Muestra:
- ListBox con el historial de mensajes (usuario y modelo).
- Cuadro de texto para escribir mensajes.
- Botón para enviar.
- Botón para editar el Prompt de Sistema.
- Botón para cerrar la aplicación.
- Atajos de teclado para:
* Enfocar la lista de mensajes (Alt+L).
* Enfocar el cuadro de chat (Alt+C).
"""

def __init__(self, parent=None, modelo="deepseek-r1:1.5b"):
super().__init__(parent, title=f"Chat con Ollama - Modelo: {modelo}", size=(600, 500))

self.modelo = modelo
self.model_display_name = extraer_nombre_modelo(modelo)
self.system_prompt = ""
self.messages = [] # Lista de tuplas (rol, contenido)

self.panel = wx.Panel(self)
# Controles
self.message_list = wx.ListBox(self.panel, style=wx.LB_SINGLE, size=(550, 200))
self.user_input = wx.TextCtrl(self.panel, style=wx.TE_MULTILINE, size=(550, 100))

self.btn_enviar = wx.Button(self.panel, label="&Enviar")
self.btn_editar_prompt = wx.Button(self.panel, label="Editar &Prompt de Sistema")
self.btn_cerrar_app = wx.Button(self.panel, label="&Salir")

# Temporizador para medir la generación de respuesta
self.response_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.on_timer_tick, self.response_timer)
self.response_time_start = 0.0
self.index_pending_response = None

# Eventos de botones
self.btn_enviar.Bind(wx.EVT_BUTTON, self.on_send)
self.btn_editar_prompt.Bind(wx.EVT_BUTTON, self.on_edit_prompt)
self.btn_cerrar_app.Bind(wx.EVT_BUTTON, self.on_close_app)

# Eventos de la lista
self.message_list.Bind(wx.EVT_LISTBOX_DCLICK, self.on_message_detail)
self.message_list.Bind(wx.EVT_KEY_DOWN, self.on_list_key_down)

# Layout
sizer_principal = wx.BoxSizer(wx.VERTICAL)

sizer_principal.Add(self.message_list, 1, wx.ALL wx.EXPAND, 5)
sizer_principal.Add(self.user_input, 0, wx.ALL wx.EXPAND, 5)

sizer_botones = wx.BoxSizer(wx.HORIZONTAL)
sizer_botones.Add(self.btn_enviar, 0, wx.ALL, 5)
sizer_botones.Add(self.btn_editar_prompt, 0, wx.ALL, 5)
sizer_botones.Add(self.btn_cerrar_app, 0, wx.ALL, 5)

sizer_principal.Add(sizer_botones, 0, wx.ALIGN_CENTER)
self.panel.SetSizer(sizer_principal)

# Atajos de teclado (Alt+L para la lista, Alt+C para el chat)
# Creamos IDs para los comandos de menú "ficticios"
ID_FOCUS_LIST = wx.NewIdRef()
ID_FOCUS_CHAT = wx.NewIdRef()

# Definimos la tabla de atajos
accelerators = [
(wx.ACCEL_ALT, ord('L'), ID_FOCUS_LIST),
(wx.ACCEL_ALT, ord('C'), ID_FOCUS_CHAT),
]
accel_tbl = wx.AcceleratorTable(accelerators)
self.SetAcceleratorTable(accel_tbl)

# Vinculamos eventos de menú a métodos que pongan el foco
self.Bind(wx.EVT_MENU, self.on_focus_list, id=ID_FOCUS_LIST)
self.Bind(wx.EVT_MENU, self.on_focus_chat, id=ID_FOCUS_CHAT)

self.Centre()

# Métodos para enfocar controles con atajos de teclado
def on_focus_list(self, event):
self.message_list.SetFocus()

def on_focus_chat(self, event):
self.user_input.SetFocus()

def on_send(self, event):
user_text = self.user_input.GetValue().strip()
if user_text:
# 1. Agregar el mensaje del usuario a 'messages' y a la lista
self.messages.append(("Tú", user_text))
self.message_list.Append(f"Tú: {user_text}")
self.user_input.Clear()

# 2. Agregamos un marcador de mensaje "en generación" en 'messages'
# para que el índice coincida con el de la ListBox
self.messages.append((self.model_display_name, "[Generando respuesta]"))
self.index_pending_response = len(self.messages) - 1

# 3. Insertamos en la lista la indicación de "generando"
self.message_list.Append(f"{self.model_display_name}: [Generando respuesta - 0s]")
index_list = self.message_list.GetCount() - 1 # índice del último elemento
# Aseguramos que index_pending_response y index_list sean equivalentes
# (debido a que se agregaron 2 mensajes a self.messages,
# su índice final coincide con index_list).

# 4. Iniciar temporizador
self.response_time_start = time.time()
self.response_timer.Start(1000)

# 5. Lanzar el hilo que obtendrá la respuesta
threading.Thread(target=self.handle_ollama_interaction, args=(user_text, index_list)).start()

def on_edit_prompt(self, event):
dialog = SystemPromptDialog(self, prompt_actual=self.system_prompt)
if dialog.ShowModal() == wx.ID_OK:
self.system_prompt = dialog.get_prompt()
dialog.Destroy()

def on_close_app(self, event):
self.Close()
wx.GetApp().ExitMainLoop()

def on_list_key_down(self, event):
if event.GetKeyCode() == wx.WXK_RETURN:
self.on_message_detail(None)
else:
event.Skip()

def on_message_detail(self, event):
seleccion = self.message_list.GetSelection()
if seleccion != wx.NOT_FOUND:
# Aseguramos que no haya desfasamiento en el índice
if seleccion < len(self.messages):
rol, contenido = self.messages[seleccion]
MessageDetailFrame(self, title=f"Detalle - {rol}", content=contenido)

def on_timer_tick(self, event):
"""
Cada segundo, se actualiza el texto en la ListBox que indica
"Generando respuesta - Xs".
"""
if self.index_pending_response is not None:
elapsed = int(time.time() - self.response_time_start)
# Actualiza solo el mensaje correspondiente en la ListBox
if elapsed < 3600: # Por si algo se atora, evitar overflow
txt = f"{self.model_display_name}: [Generando respuesta - {elapsed}s]"
# El índice en la ListBox es el mismo que en self.messages
self.message_list.SetString(self.index_pending_response, txt)

def handle_ollama_interaction(self, user_text, list_index):
"""
Hilo que llama a Ollama y, cuando la respuesta llega,
actualiza el historial y la interfaz en el hilo principal.
"""
try:
response = self.get_ollama_response(user_text)
wx.CallAfter(self.finish_response, response)
except Exception as e:
wx.CallAfter(self.show_error, e)

def finish_response(self, response):
"""
Se llama cuando ya tenemos la respuesta del modelo.
Detenemos el temporizador y actualizamos el texto en la ListBox
y en self.messages correspondiente a la respuesta del modelo.
"""
self.response_timer.Stop()
total_time = time.time() - self.response_time_start

# Si el modelo contiene "r1", removemos contenido <think>...</think>
if "r1" in self.modelo.lower():
response = eliminar_texto_entre_think(response)

# Sustituir el contenido en self.messages
if self.index_pending_response is not None:
self.messages[self.index_pending_response] = (self.model_display_name, response)

# Actualizar la ListBox en la misma posición
final_text = f"{self.model_display_name}: {response} (tardó {round(total_time, 2)}s)"
self.message_list.SetString(self.index_pending_response, final_text)
self.index_pending_response = None

def show_error(self, error):
"""
En caso de error, detenemos el temporizador y mostramos un mensaje de error.
"""
if self.response_timer.IsRunning():
self.response_timer.Stop()
wx.MessageBox(f"Error al comunicarse con Ollama: {error}", "Error", wx.OK | wx.ICON_ERROR)

def get_ollama_response(self, prompt):
"""
Llama a la API de Ollama y retorna la respuesta.
"""
url = "http://localhost:11434/api/generate"
headers = {"Content-Type": "application/json"}
data = {
"model": self.modelo,
"system": self.system_prompt,
"prompt": prompt,
"stream": False
}

resp = requests.post(url, headers=headers, json=data)
if resp.status_code == 200:
try:
return resp.json().get("response", "No se recibió respuesta de Ollama.")
except json.JSONDecodeError as e:
contenido = resp.text.strip()
if contenido:
for linea in contenido.splitlines():
linea = linea.strip()
if linea:
try:
json_line = json.loads(linea)
return json_line.get("response", "No se recibió respuesta en la línea.")
except json.JSONDecodeError:
continue
raise Exception(f"Error al decodificar la respuesta JSON: {e}")
else:
raise Exception(f"Error {resp.status_code}: {resp.text}")

class MyApp(wx.App):
def OnInit(self):
"""
Al iniciar la aplicación:
1. Levanta el servidor de Ollama con 'ollama serve'.
2. Muestra la ventana de selección de modelo.
"""

frame = ModeloSelectionFrame()
frame.Show()
return True

def OnExit(self):
"""
Cuando la app sale, cerramos el servidor de Ollama.
"""
if self.server_process and self.server_process.poll() is None:
self.server_process.terminate()
return super().OnExit()

if __name__ == "__main__":
app = MyApp(False)
app.MainLoop()
sys.exit(0)

Skor: -1

6. Exink,

Me perdiste con el "bienvenides todes", vamos, que ni las IAs más progres empiezan de esa forma, así que te han timado con esa declaración tan grande. Y, ahora, seré tan hijo de puta como para darte un -1.

Igual, yo prefiero usar los modelos con alguna extensión como Page assist, ya que renderiza mejor el Markdown y no se ve tan rara la cosa.

Skor: +0

Poslednja izmena od strane Exink, nedelja 19:07

7. Rayo,

no creo que seas tan lloron, o... o sí? o sí?
sí, yo igual estoy pensando buscarme algo que renderice el markdown o vamos, hacerlo yo porque si de por sí la pc que tengo corre muy a tirones los modelos, cargarle una gui pesada será algo catastrófico. no se si alguien haya probado ml studio.

Skor: -1

8. Exink,

Pues si eso crees... Cada quién cree lo que quiere creer y habla como quiere, aunque sea con cierta falsa inclusión, por lo visto, así que...

Nunca supe cómo era usar MLStudio, creí que habría que instalar Cuda o esas cosas. Tengo R-1 14b y la computadora se calienta a lo bestia, pero al menos funciona, que ya es ganancia. El de 7b no me pareció suficiente, a menos que lo pruebe con otro tipo de tareas.

Skor: +0

Poslednja izmena od strane Exink, nedelja 21:58

9. ruray,

para los más abanzados, recomiendo usar lm studio para descargar los modelos y anything llm para interactuar con ellos. Yo lo hice así porque la terminal y yo no nos llevamos muy bien que digamos.

Skor: +0

10. Rayo,

qué tanto cambia lm studio en cuestión de consumo de ram?
pensé probarlo, pero lo dicho, no se si mis recursos actuales den para algo así.

Skor: +0

11. ruray,

diría que es lo mismo que Ollama, la app es muy lijera. Yo tengo 16 gb y puedo correr modelos de 14b sin problema.

Skor: +0

12. Exink,

A menos que tengas que escribir supercomandos con un montón de parámetros, la terminal no debería ser tan complicada. Yo lo uso así por el tema del Markdown y porque visualmente no es tan fea, pero en la terminal también puedes limpiar todo, cargar sesiones, etc.

Skor: +0

Poslednja izmena od strane Exink, nedelja 22:24

13. Rayo,

hmm me quedo con consola y el script que he hecho. 12 gb de ram y a dduras penas corro modelos de 7b.

Skor: +0

13 poruka, 1 stranica:  1 ↖ Vrati se nazad na listu tema

Odgovori na temu

Morate da se prijavite kako biste mogli da pišete

Zaboravljena lozinka? Napravi nalog