En este tutorial, trabajamos directamente con modelos Qwen3.5 destilados con un razonamiento estilo Claude y configuramos una canalización de Colab que nos permite cambiar entre una variante 27B GGUF y una versión liviana 2B de 4 bits con una sola bandera. Comenzamos validando la disponibilidad de la GPU, luego instalamos condicionalmente llama.cpp o transformadores con bitsandbytes, según la ruta seleccionada. Ambas ramas están unificadas a través de interfaces compartidas generate_fn y stream_fn, lo que garantiza una inferencia coherente entre los backends. También implementamos una clase ChatSession para interacción de múltiples turnos y creamos utilidades para analizar seguimientos, lo que nos permite separar explícitamente el razonamiento de los resultados finales durante la ejecución.
MODEL_PATH = "2B_HF" importar antorcha si no es torch.cuda.is_available(): elevar RuntimeError ("❌ ¡No hay GPU! Vaya a Runtime → Cambiar tipo de tiempo de ejecución → T4 GPU." ) gpu_name = torch.cuda.get_device_name(0) vram_gb = torch.cuda.get_device_properties(0).total_memory / 1e9 print(f"✅ GPU: {gpu_name} — {vram_gb:.1f} GB VRAM") subproceso de importación, sys, os, re, time generate_fn = Ninguno stream_fn = Ninguno
Inicializamos la ejecución configurando el indicador de ruta del modelo y verificando si hay una GPU disponible en el sistema. Recuperamos e imprimimos el nombre de la GPU junto con la VRAM disponible para garantizar que el entorno cumpla con los requisitos. También importamos todas las bibliotecas base necesarias y definimos marcadores de posición para las funciones de generación unificadas que se asignarán más adelante.
if MODEL_PATH == "27B_GGUF": print("n📦 Instalar llama-cpp-python con CUDA (tarda de 3 a 5 minutos)…") env = os.environ.copy() env["CMAKE_ARGS"] = "-DGGML_CUDA=on" subprocess.check_call( [sys.executable, "-m", "pip", "install", "-q", "llama-cpp-python", "huggingface_hub"], env=env, ) print("✅ Instalado.n") from huggingface_hub import hf_hub_download from llama_cpp import Llama GGUF_REPO = "Jackrong/Qwen3.5-27B-Claude-4.6-Opus-Reasoning-Distilled-GGUF" GGUF_FILE = "Qwen3.5-27B-Claude-4.6-Opus-Reasoning-Distilled-Q4_K_M.gguf" print(f"⏳ Descargando {GGUF_FILE} (~16.5 GB)… toma un café ☕") model_path = hf_hub_download(repo_id=GGUF_REPO, filename=GGUF_FILE) print(f"✅ Descargado: {model_path}n") print("⏳ Cargando en llama.cpp (descarga de GPU)…") llm = Llama( model_path=model_path, n_ctx=8192, n_gpu_layers=40, n_threads=4, verbose=False, ) print("✅ Modelo 27B GGUF cargado!n") def generate_fn( Prompt, system_prompt="Usted es un asistente útil Piense paso a paso.", max_new_tokens=2048, temperatura=0.6, top_p=0.95, **kwargs): salida = llm.create_chat_completion( mensajes=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": Prompt}, ], max_tokens=max_new_tokens, temperatura=temperatura, top_p=top_p, ) devuelve salida["opciones"][0]["message"]["content"] def stream_fn( Prompt, system_prompt="Eres un asistente útil. Piensa paso a paso.", max_new_tokens=2048, Temperature=0.6, top_p=0.95, ): print("⏳ Salida de streaming:n") para fragmento en llm.create_chat_completion( mensajes=[ {"role": "system", "content": system_prompt}, {"rol": "usuario", "contenido": aviso}, ], max_tokens=max_new_tokens, temperatura=temperatura, top_p=top_p, flujo=Verdadero,): delta = fragmento["opciones"][0].get("delta", {}) text = delta.get("content", "") if text: print(text, end="", Flush=True) print() class ChatSession: def __init__(self, system_prompt="Eres un asistente útil. Piensa paso a paso."): self.messages = [{"role": "system", "content": system_prompt}] def chat(self, user_message, Temperature=0.6): self.messages.append({"role": "usuario", "content": user_message}) salida = llm.create_chat_completion( mensajes=self.messages, max_tokens=2048, temperatura=temperatura, top_p=0.95, ) resp = salida["opciones"][0]["mensaje"]["contenido"] self.messages.append({"role": "asistente", "contenido": resp}) return resp
Manejamos la ruta 27B GGUF instalando llama.cpp con soporte CUDA y descargando el modelo destilado Qwen3.5 27B de Hugging Face. Cargamos el modelo con descarga de GPU y definimos un generate_fn y un stream_fn estandarizados para inferencia y salidas de transmisión. También implementamos una clase ChatSession para mantener el historial de conversaciones para interacciones de varios turnos.
elif MODEL_PATH == "2B_HF": print("n📦 Instalando transformadores + bitsandbytes…") subprocess.check_call([ sys.executable, "-m", "pip", "install", "-q", "transformers @ git+https://github.com/huggingface/transformers.git@main", "accelerate", "bitsandbytes", "sentencepiece", "protobuf", ]) print("✅ Instalado.n") desde transformadores import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, TextStreamer HF_MODEL_ID = "Jackrong/Qwen3.5-2B-Claude-4.6-Opus-Reasoning-Distilled" bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True, ) print(f"⏳ Cargando {HF_MODEL_ID} en 4 bits…") tokenizer = AutoTokenizer.from_pretrained(HF_MODEL_ID, trust_remote_code=True) modelo = AutoModelForCausalLM.from_pretrained( HF_MODEL_ID, quantization_config=bnb_config, device_map="auto", trust_remote_code=True, torch_dtype=torch.bfloat16, ) print(f"✅ ¡Modelo cargado! Memoria: {model.get_memory_footprint() / 1e9:.2f} GBn") def generate_fn( solicitud, system_prompt="Eres un asistente útil. Piensa paso a paso.", max_new_tokens=2048, Temperature=0.6, top_p=0.95, repetition_penalty=1.05, do_sample=True, **kwargs): mensajes = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": Prompt}, ] text = tokenizer.apply_chat_template(messages, tokenize=False, add_spawn_prompt=True) inputs = tokenizer(text, return_tensors="pt").to(model.device) con torch.no_grad(): output_ids = model.generate( **inputs, max_new_tokens=max_new_tokens, temperatura=temperatura, top_p=top_p, repetition_penalty=repetition_penalty, do_sample=do_sample, ) generado = output_ids[0][entradas["input_ids"].forma[1]:] return tokenizer.decode(generated, skip_special_tokens=True) def stream_fn( Prompt, system_prompt="Eres un asistente útil. Piensa paso a paso.", max_new_tokens=2048, Temperature=0.6, top_p=0.95, ): mensajes = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": Prompt}, ] texto = tokenizer.apply_chat_template(messages, tokenize=False, add_spawn_prompt=True) inputs = tokenizer(text, return_tensors="pt").to(model.device) streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True) print("⏳ Salida de streaming:n") con torch.no_grad(): model.generate( **inputs, max_new_tokens=max_new_tokens, temperatura=temperatura, top_p=top_p, do_sample=True, streamer=streamer, ) class ChatSession: def __init__(self, system_prompt="Eres un asistente útil. Piensa paso a paso."): self.messages = [{"role": "system", "content": system_prompt}] def chat(self, mensaje_usuario, temperatura=0.6): self.messages.append({"role": "usuario", "content": mensaje_usuario}) texto = tokenizer.apply_chat_template(self.messages, tokenize=False, add_generación_prompt=True) entradas = tokenizer(texto, return_tensors="pt").to(model.device) con torch.no_grad(): output_ids = model.generate( **entradas, max_new_tokens=2048, temperatura=temperatura, top_p=0.95, do_sample=True, ) generado = output_ids[0][entradas["input_ids"].forma[1]:] resp = tokenizer.decode(generated, skip_special_tokens=True) self.messages.append({"role": "assistant", "content": resp}) return resp else: rise ValueError("MODEL_PATH debe ser '27B_GGUF' o '2B_HF'")
Implementamos la ruta ligera 2B utilizando transformadores con cuantificación de 4 bits mediante bitsandbytes. Cargamos el modelo destilado Qwen3.5 2B de manera eficiente en la GPU y configuramos los parámetros de generación para un muestreo controlado. Nuevamente definimos la lógica unificada de generación, transmisión y sesión de chat para que ambas rutas del modelo se comporten de manera idéntica durante la ejecución.
def parse_thinking(respuesta: str) -> tupla: m = re.search(r"(.*?)", respuesta, re.DOTALL) if m: return m.group(1).strip(), respuesta[m.end():].strip() return "", respuesta.strip() def display_response(respuesta: str): pensando, respuesta = parse_thinking(respuesta) if pensando: print("🧠 PENSAR:") print("-" * 60) print(thinking[:1500] + ("n… [truncado]" if len(thinking) > 1500 else "")) print("-" * 60) print("n💬 RESPUESTA:") print(respuesta) print("✅ Todos los ayudantes listos. Ejecutando pruebas…n")
Definimos funciones auxiliares para extraer rastros de razonamiento encerrados dentro de etiquetas y separarlas de las respuestas finales. Creamos una utilidad de visualización que formatea e imprime tanto el proceso de pensamiento como la respuesta de forma estructurada. Esto nos permite inspeccionar cómo el modelo basado en Qwen razona internamente durante la generación.
print("=" * 70) print("📝 PRUEBA 1: Razonamiento básico") print("=" * 70) respuesta = generate_fn( "Si tengo 3 manzanas y regalo la mitad, luego compro 5 más, ¿cuántas tengo? " "Explica tu razonamiento." ) display_response(respuesta) print("n" + "=" * 70) print("📝 PRUEBA 2: Salida en streaming") print("=" * 70) stream_fn( "Explica la diferencia entre concurrencia y paralelismo. " "Da una analogía del mundo real para cada uno." ) print("n" + "=" * 70) print("📝 PRUEBA 3: Pensar ON versus OFF") print("=" * 70) question = "¿Cuál es la capital de Francia?" print("n— Pensando ON (predeterminado) —") resp = generate_fn(question) display_response(resp) print("n— Pensando OFF (conciso) —") resp = generate_fn( question, system_prompt="Responda directa y concisa. No use etiquetas.", max_new_tokens=256, ) display_response(resp) print("n" + "=" * 70) print("📝 PRUEBA 4: Pregunta con truco de bate y pelota") print("=" * 70) respuesta = generate_fn( "Un bate y una pelota cuestan $1.10 en total. " "¿Cuánto cuesta la pelota? Muestra el razonamiento completo y verifica.", system_prompt="Eres un razonador matemático preciso. Establece ecuaciones y verifica.", temperatura=0.3,) display_response(respuesta) print("n" + "=" * 70) print("📝 PRUEBA 5: Problema de encuentro de trenes") print("=" * 70) respuesta = generate_fn( "Un tren sale de la estación A a las 9:00 a. m. a 60 mph hacia la estación B. " "Otro sale de la estación B a las 10:00 a. m. a 80 mph hacia la estación A. " "Las estaciones están a 280 millas de distancia. ¿Cuándo y ¿dónde se encuentran?", temperatura=0.3, ) display_response(respuesta) print("n" + "=" * 70) print("📝 TEST 6: Rompecabezas de lógica (cinco casas)") print("=" * 70) respuesta = generate_fn( "Cinco casas seguidas están pintadas de diferentes colores. " "La casa roja está a la izquierda de la casa azul. " "La casa verde está en el medio. " "La casa amarilla no está al lado de la azul house. " "La casa blanca está en un extremo. " "¿Cuál es el orden de izquierda a derecha?", temperatura=0.3, max_new_tokens=3000, ) display_response(response) print("n" + "=" * 70) print("📝 PRUEBA 7: Generación de código: subcadena palindrómica más larga") print("=" * 70) respuesta = generate_fn( "Escribe una función de Python para encontrar la más larga subcadena palindrómica " "usando el algoritmo de Manacher. Incluye cadena de documentación, sugerencias de tipo y pruebas.", system_prompt="Eres un programador experto en Python. Piensa detenidamente en el algoritmo.", max_new_tokens=3000, temperatura=0.3, ) display_response(response) print("n" + "=" * 70) print("📝 PRUEBA 8: Conversación de varios turnos (tutor de física)") print("=" * 70) session = ChatSession( system_prompt="Eres un tutor de física experto. Explícalo claramente con ejemplos." ) turns = [ "¿Qué es el principio de incertidumbre de Heisenberg?", "¿Puedes darme un ejemplo concreto con números reales?", "¿Cómo se relaciona esto con el túnel cuántico?", ] para i, q en enumerate(turns, 1): print(f"n{'─'*60}") print(f"👤 Turn {i}: {q}") print(f"{'─'*60}") resp = session.chat(q, temperatura=0.5) _, respuesta = parse_thinking(resp) print(f"🤖 {answer[:1000]}{'…' if len(respuesta) > 1000 else ''}") print("n" + "=" * 70) print("📝 TEST 9: Temperatura comparación – escritura creativa") print("=" * 70) creative_prompt = "Escribe una introducción de un párrafo para una historia de ciencia ficción sobre la conciencia de la IA." configs = [ {"label": "Temperatura baja (0.1)", "temperatura": 0.1, "top_p": 0.9}, {"label": "Temperatura media (0.6)", "temperatura": 0.6, "top_p": 0.95}, {"label": "Temperatura alta (1.0)", "temperatura": 1.0, "top_p": 0.98}, ] para cfg en configuraciones: print(f"n🎛️ {cfg['label']}") print("-" * 60) start = time.time() resp = generate_fn( creative_prompt, system_prompt="Eres un escritor de ficción creativo.", max_new_tokens=512, Temperature=cfg["temperature"], top_p=cfg["top_p"], ) transcurrido = time.time() – start _, respuesta = parse_thinking(resp) print(answer[:600]) print(f"⏱️ {elapsed:.1f}s") print("n" + "=" * 70) print("📝 PRUEBA 10: Prueba comparativa de velocidad") print("=" * 70) start = time.time() resp = generate_fn( "Explica cómo aprende una red neuronal, paso a paso, para un principiante.", system_prompt="Eres un paciente, claro maestro.", max_new_tokens=1024, ) transcurrido = time.time() – start approx_tokens = int(len(resp.split()) * 1.3) print(f"~{approx_tokens} tokens in {elapsed:.1f}s") print(f"~{approx_tokens / transcurrido:.1f} tokens/seg") print(f"GPU: {torch.cuda.get_device_name(0)}") print(f"VRAM pico: {torch.cuda.max_memory_allocated() / 1e9:.2f} GB") import gc para el nombre en ["model", "llm"]: si el nombre está en globals(): del globals()[nombre] gc.collect() torch.cuda.empty_cache() print(f"n✅ Memoria liberada. VRAM: {torch.cuda.memory_allocated() / 1e9:.2f} GB") print("n" + "=" * 70) print("🎉 ¡Tutorial completo!") print("=" * 70)
Ejecutamos un conjunto de pruebas integral que evalúa el modelo mediante razonamiento, transmisión, acertijos lógicos, generación de código y conversaciones de varios turnos. Comparamos salidas bajo diferentes configuraciones de temperatura y medimos el rendimiento en términos de velocidad y rendimiento del token. Finalmente, limpiamos la memoria y liberamos recursos de la GPU, asegurando que la computadora portátil siga siendo reutilizable para futuros experimentos.
En conclusión, tenemos una configuración compacta pero flexible para ejecutar modelos de razonamiento basados en Qwen3.5 mejorados con destilación estilo Claude a través de diferentes restricciones de hardware. El script abstrae las diferencias de backend al tiempo que expone interfaces de generación, transmisión y conversación consistentes, lo que facilita experimentar con el comportamiento de razonamiento. A través del conjunto de pruebas, probamos cómo el modelo maneja el razonamiento estructurado, las preguntas de casos extremos y las tareas más largas de varios pasos, al mismo tiempo que medimos la velocidad y el uso de la memoria. Lo que obtenemos no es solo una demostración, sino una plataforma reutilizable para evaluar y ampliar los sistemas de razonamiento basados en Qwen en Colab sin cambiar el código central.
Consulte el cuaderno completo y la página fuente. Además, no dude en seguirnos en Twitter y no olvide unirse a nuestro SubReddit de más de 120.000 ML y suscribirse a nuestro boletín. ¡Esperar! estas en telegrama? Ahora también puedes unirte a nosotros en Telegram.