Everything you wanted to know about Deep Learning and Large Language Models but were afraid to ask
Université de Montpellier, IMAG
CNRS
Inria
LIRMM
Université
CNRS
LIRMM
Université de Montpellier
de
Montpellier
2027-04-07
AI was all about symbolic reasoning until the 1980s: culmination in “Expert Systems”, which are abandoned:
\Rightarrow 🧠 an alternative neuro-computing branch of AI, but…
… in mid ’80s they seem to have both failed to deliver on their promises. 🤷♂️
Artificial Intelligence (AI)
Machine Learning (ML)
Deep Learning (DL)
Large Language Models (LLM)

📜 More or less theoretical guarantees
🛠️ Myriad of ad-hoc choices, engineering tricks and empirical observations
🚦 Current choices are critical for success: what are their pros and cons?
🔄 Try ➔ ❌ Fail ➔ 🔁 Try again is the current pipeline


Criticizing an entire community (and an incredibly successful one at that) for practicing “alchemy” 🧪, simply because our current theoretical tools haven’t caught up with our practice is dangerous. Why dangerous? It’s exactly this kind of attitude that led the ML community to abandon neural nets for over 10 years, despite ample empirical evidence that they worked very well in many situations. (Yann LeCun, 2017, My take on Ali Rahimi’s “Test of Time” award talk at NIPS.)
Also, on hardware side:

shape=(batch, height, width, features)
f(x)=\phi_{L}\!\Big(W_{L}\,\phi_{L-1}\big(W_{L-1}\,\cdots \phi_{1}(W_{1}x+b_{1})\cdots + b_{L-1}\big)+ b_{L}\Big)
\Rightarrow input can be anything: images, videos, text, sound, …
x = \mathrm{vec}\!\Big( \underbrace{T_{\text{img}}}_{\in \mathbb{R}^{H\times W\times C}} \;\Vert\; \underbrace{T_{\text{text}}}_{\in \mathbb{R}^{L\times d_w}} \;\Vert\; \underbrace{T_{\text{audio}}}_{\in \mathbb{R}^{T\times d_a}} \;\Vert\; \underbrace{T_{\text{video}}}_{\in \mathbb{R}^{F\times H'\times W'\times C'}} \Big)
f(x)=\nabla\frac{x_{1}x_{2} sin(x_3) +e^{x_{1}x_{2}}}{x_3}

\begin{darray}{rcl} x_4 & = & x_{1}x_{2}, \\ x_5 & = & sin(x_3), \\ x_6 & = & e^{x_4}, \\ x_7 & = & x_{4}x_{5}, \\ x_8 & = & x_{6}+x_7, \\ x_9 & = & x_{8}/x_3. \end{darray}
\theta_{k+1} \leftarrow \theta_k - \eta \nabla_\theta \mathcal{L}(f_\theta(x_i), y_i)
\Rightarrow 🧭 Sensitive to initial point and hyperparameters
Example with a non-convex function (sum of Gaussians with random signs)
viewof number_of_gaussians = Inputs.range([1, 20], {step: 1, value: 7, label: 'number of Gaussians (n)'});
minX = -5;
maxX = 5;
function f(x, y, opts = {}) {
// options
const n = opts.n ?? number_of_gaussians; // number of Gaussians
const seed = opts.seed ?? 123456; // deterministic seed
const ampMin = opts.ampMin ?? 0.6;
const ampMax = opts.ampMax ?? 3.0;
const sigmaMin = opts.sigmaMin ?? 0.25;
const sigmaMax = opts.sigmaMax ?? 2.0;
const baseline = opts.baseline ?? 0.01; // mild quadratic baseline weight
// seeded LCG RNG (deterministic)
function lcg(s) {
let state = s >>> 0;
return () => {
state = (1664525 * state + 1013904223) >>> 0;
return state / 0x100000000;
};
}
const rnd = lcg(seed);
// generate centers, amps, sigmas and signs deterministically
const centers = new Array(n);
const amps = new Array(n);
const sigmas = new Array(n);
const signs = new Array(n);
for (let i = 0; i < n; ++i) {
const cx = -5 + 10 * rnd(); // in [-5,5]
const cy = -5 + 10 * rnd();
centers[i] = [cx, cy];
amps[i] = ampMin + (ampMax - ampMin) * rnd();
sigmas[i] = sigmaMin + (sigmaMax - sigmaMin) * rnd();
// random sign: -1 or +1 (deterministic via seed)
signs[i] = rnd() < 0.5 ? -1 : 1;
}
// baseline to keep global shape smooth
let out = x.square().add(y.square()).mul(baseline);
// sum of Gaussians with random sign (wells or bumps)
for (let i = 0; i < n; ++i) {
const [cx, cy] = centers[i];
const A = amps[i];
const s = sigmas[i];
const sign = signs[i];
const dx = x.sub(cx);
const dy = y.sub(cy);
const r2 = dx.square().add(dy.square());
const gauss = tf.exp(r2.neg().div(2 * s * s)).mul(A);
// subtract signed gaussian so sign=-1 -> add well, sign=+1 -> subtract (well)
out = out.sub(gauss.mul(sign));
}
return out;
}
function tf_f(x, y, opts = {}) {
return tf.tidy(() => f(x,y, opts));
}
X1 = tf.linspace(minX, maxX, 50).arraySync();
X2 = tf.linspace(minX, maxX, 50).arraySync();
// shape: [ny, nx] where nx = X1.length, ny = X2.length
nx = X1.length;
ny = X2.length;
meshX = tf.tile(tf.tensor(X1).reshape([1, nx]), [ny, 1]); // each row contains X1 across columns
meshY = tf.tile(tf.tensor(X2).reshape([ny, 1]), [1, nx]); // each row contains the same Y value across columns
Z = tf_f(meshX, meshY).arraySync();
mutable surfaceDivRendered = false;
surfaceDiv = {
const data = [{
x: X1,
y: X2,
z: tf.zeros([50, 50]).arraySync(),
type: 'surface',
showscale: false,
hoverinfo: 'skip',
}];
const layout = {
autosize: false,
width: 400,
height: 400,
legend: false,
paper_bgcolor: "rgba(0,0,0,0)",
plot_bgcolor: "rgba(0,0,0,0)",
template: 'plotly_dark',
margin: {
l: 65,
r: 50,
b: 65,
t: 90,
},
scene: {
camera : {
up: {x: 0, y: 0, z: 1},
center: {x: 0, y: 0, z: 0},
eye: {x: -1.25, y: -1.25, z: 0.6}
}
},
};
const div = document.createElement('div');
Plotly.newPlot(div, data, layout,{displayModeBar: false});
div.style.marginTop = "-80px";
return div;
}
{
if (number_of_gaussians) {
mutable surfaceDivRendered = false;
}
return html``;
}
{
Plotly.update(surfaceDiv, {z: [Z]});
mutable surfaceDivRendered = true;
// console.log("surface updated.");
return html``;
}
// function grad_descent(x1, x2, step, max_iter) {
// // gradsFn returns a function that takes [x1, x2] and returns [df/dx1, df/dx2]
// const gradsFn = tf.grads(tf_f);
// let point = [x1, x2];
// let iterations = [point.slice()];
// var count = 0;
// while (count < max_iter) {
// const grad = gradsFn(point);
// point[0] -= step * grad[0].arraySync();
// point[1] -= step * grad[1].arraySync();
// if (
// isFinite(point[0]) && isFinite(point[1]) &&
// (minX < point[0]) && (point[0] < maxX) &&
// (minX < point[1]) && (point[1] < maxX)
// ) {
// iterations.push(point.slice());
// } else {
// iterations.push(iterations[count]);
// }
// count += 1;
// }
// return iterations;
// }
// Generalized gradient descent / optimizer implementation
function grad_descent(x1, x2, lr, max_iter = 30, optConfig = {}) {
// optConfig: { optimizer: 'sgd'|'momentum'|'rmsprop'|'adam', momentum, decay, beta1, beta2, eps }
const optName = (optConfig && optConfig.optimizer) || 'sgd';
let optimizer;
// build optimizer using tf.train.*
if (optName === 'sgd') {
optimizer = tf.train.sgd(lr);
} else if (optName === 'momentum') {
optimizer = tf.train.momentum(lr, Number(optConfig.momentum || 0.9));
} else if (optName === 'rmsprop') {
// tf.train.rmsprop(learningRate, decay?, momentum?, epsilon?, centered?)
optimizer = tf.train.rmsprop(lr, Number(optConfig.decay ?? 0.9), Number(optConfig.momentum ?? 0.0), Number(optConfig.eps ?? 1e-8));
} else if (optName === 'adam') {
optimizer = tf.train.adam(lr, Number(optConfig.beta1 ?? 0.9), Number(optConfig.beta2 ?? 0.999), Number(optConfig.eps ?? 1e-8));
} else {
optimizer = tf.train.sgd(lr);
}
// variables (scalars) for the two coordinates
const xVar = tf.variable(tf.scalar(x1));
const yVar = tf.variable(tf.scalar(x2));
const iterations = [[x1, x2]];
for (let i = 0; i < max_iter; ++i) {
// run one optimizer step inside tidy to avoid leaking temporaries
tf.tidy(() => {
// minimize expects a scalar tensor; ensure tf_f returns a scalar by squeezing
optimizer.minimize(() => tf.squeeze(tf_f(xVar, yVar)), /* returnCost= */ false, [xVar, yVar]);
});
// read back the updated variable values synchronously
const xv = xVar.dataSync()[0];
const yv = yVar.dataSync()[0];
if (
isFinite(xv) && isFinite(yv) &&
(minX < xv) && (xv < maxX) &&
(minX < yv) && (yv < maxX)
) {
iterations.push([xv, yv]);
} else {
// if out of bounds or invalid, repeat previous point
iterations.push(iterations[iterations.length - 1]);
}
}
// clean up
xVar.dispose();
yVar.dispose();
if (optimizer.dispose) optimizer.dispose();
return iterations;
}
x1_0 = 0;
x2_0 = 0;
step_size = 1.5;
// viewof descent_params = Inputs.form({
// x1: Inputs.range([minX, maxX], {step: 0.1, value: x1_0, label: 'x1'}),
// x2: Inputs.range([minX, maxX], {step: 0.1, value: x2_0, label: 'x2'}),
// step: Inputs.range([0.001, 3.0], {step: 0.3, value: step_size, label: 'step_size'})
// })
// form with optimizer selection and hyperparameters
viewof descent_params = {
const optimizer_menu = Inputs.select(['sgd', 'momentum', 'rmsprop', 'adam'], {value: 'sgd', label: 'optimizer'});
optimizer_menu.querySelector('select').style.cssText = "font-size: 0.6em; background-color: darkgray;";
return Inputs.form({
x1: Inputs.range([minX, maxX], {step: 0.1, value: x1_0, label: 'x1'}),
x2: Inputs.range([minX, maxX], {step: 0.1, value: x2_0, label: 'x2'}),
step: Inputs.range([0.001, 3.0], {step: 0.01, value: step_size, label: 'learning rate'}),
optimizer: optimizer_menu,
// momentum for SGD+momentum
momentum: Inputs.range([0.0, 0.99], {step: 0.01, value: 0.9, label: 'momentum (for momentum)'}),
// RMSProp / Adam parameters
decay: Inputs.range([0.0, 0.999], {step: 0.001, value: 0.9, label: 'decay (rmsprop)'}),
beta1: Inputs.range([0.0, 0.999], {step: 0.001, value: 0.9, label: 'beta1 (adam)'}),
beta2: Inputs.range([0.0, 0.9999], {step: 0.0001, value: 0.999, label: 'beta2 (adam)'}),
eps: Inputs.range([1e-8, 1e-3], {step: 1e-8, value: 1e-8, label: 'eps (stability)'}),
}, { columns: 2 });
}
graddivplot = {
// // Prepare the gradient descent pat
// // Contour plot
// const contour = {
// x: X1,
// y: X2,
// z: Z,
// hovertemplate: "%{z:.1f}",
// type: 'contour',
// colorscale: 'RdBu',
// ncontours: 50,
// showscale: false,
// name: 'Loss'
// };
// const layout = {
// width: 320,
// height: 320,
// xaxis: { title: 'x1 →', tickmode: 'auto', range: [minX, maxX] },
// yaxis: { title: 'x2 →', tickmode: 'auto', range: [minX, maxX] },
// paper_bgcolor: "rgba(0,0,0,0)",
// plot_bgcolor: "rgba(0,0,0,0)",
// margin: { l: 40, r: 10, b: 40, t: 10 }
// };
// const div = document.createElement('div');
// Plotly.newPlot(div, [contour], layout, {displayModeBar: false, responsive: false});
// div.on('plotly_click', function(eventData) {
// const x = eventData.points[0].x;
// const y = eventData.points[0].y;
// (viewof descent_params).value = {x1: x, x2: y, step: descent_params.step};
// (viewof descent_params).dispatchEvent(new Event("input"), {bubbles: true});
// });
// return div;
html``;
}
// function getDescentPath(x1, x2, step) {
// const iterations = grad_descent(x1, x2, step, 30);
// const x = iterations.map(pt => pt[0]);
// const y = iterations.map(pt => pt[1]);
// const z = tf_f(tf.tensor1d(x), tf.tensor1d(y)).arraySync();
// return { x, y, z };
// }
function getDescentPath(x1, x2, step, optConfig = {}) {
const iterations = grad_descent(x1, x2, step, 30, optConfig);
const x = iterations.map(pt => pt[0]);
const y = iterations.map(pt => pt[1]);
const z = tf_f(tf.tensor1d(x), tf.tensor1d(y)).arraySync();
return { x, y, z };
}
{
if (!surfaceDivRendered) {
return html``;
}
// const numberofFlatTraces = graddivplot.data.length;
// console.log("updating traces...");
const numberof3DTraces = surfaceDiv.data.length;
// if (numberofFlatTraces > 1) {
// Plotly.deleteTraces(graddivplot, numberofFlatTraces - 1);
// }
if (numberof3DTraces > 1) {
Plotly.deleteTraces(surfaceDiv, numberof3DTraces - 1);
}
// --- replace the block that computes DescentPath and traces ---
const optConfig = {
optimizer: descent_params.optimizer,
momentum: descent_params.momentum,
decay: descent_params.decay,
beta1: descent_params.beta1,
beta2: descent_params.beta2,
eps: descent_params.eps,
};
const DescentPath = getDescentPath(descent_params.x1, descent_params.x2, descent_params.step, optConfig);
// flat 2D trace (contour overlay)
const flatTrace = {
x: DescentPath.x,
y: DescentPath.y,
hovertemplate: "x1: %{x:.1f}, x2: %{y:.1f}",
mode: 'lines+markers',
type: 'scatter',
marker: { color: '#3fb618', size: 8 },
line: { color: '#3fb618', width: 4 },
name: 'Gradient Descent'
};
const threeDTrace = {
x: DescentPath.x,
y: DescentPath.y,
z: DescentPath.z.map(z => z + 0.01), // lift a bit above the surface
type: 'scatter3d',
mode: 'lines+markers',
marker: { color: '#3fb618', size: 8 },
line: { color: '#3fb618', width: 4 },
name: 'Gradient Descent',
};
// Plotly.addTraces(graddivplot, flatTrace);
Plotly.addTraces(surfaceDiv, threeDTrace);
// console.log("traces updated.");
return html``;
}\theta_{k+1} \leftarrow \theta_k - \frac{\eta}{n}\sum_{i\in\text{batch}}\nabla_\theta \mathcal{L}(f_\theta(x_i), y_i)
\Rightarrow 🚫 No general guarantees of convergence in DL setting
SGD, Adam, RMSProp
How well do DL models generalize is one of the biggest open mysteries in ML research.
women
woman
window
widow
Tanguy Lefort, 2023
widow
women
woman
window
Tanguy Lefort, 2023
\Rightarrow 🤖 transformers capture dependencies in the “whole” 🧩
🧠 the attention mechanism extends to multifaceted dependencies of the same text components.
In the sentence :
the cat sat on the rug, and after a few hours, it moved to the mat.
\Rightarrow All those groups of words/tokens are multiple facets of the same text and its meaning. 🌈🔍
Vaswani et al. (2017)
\text{Attention}(Q,K,V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
Vaswani et al. (2017)
The head view visualizes attention across all heads in a single Transformer layer.